diff --git a/.circleci/config.yml b/.circleci/config.yml index 3cd3fa1b1..202c77830 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,7 +64,7 @@ jobs: - run: *prepare - run: | xcrun simctl create "Apple TV 1080p" com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p com.apple.CoreSimulator.SimRuntime.tvOS-11-0 - bundle exec rake test:deployment + bundle exec rake package:release jazzy: <<: *defaults steps: diff --git a/.travis.yml b/.travis.yml index de3682b4a..3040630e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,8 @@ jobs: - xcrun simctl create "Apple TV 1080p" com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p com.apple.CoreSimulator.SimRuntime.tvOS-11-2 - bundle exec rake test:deployment - ./Scripts/jazzy.sh + - xcrun simctl create "Apple TV 1080p" com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p com.apple.CoreSimulator.SimRuntime.tvOS-11-0 + - bundle exec rake package:release deploy: - provider: releases api_key: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..b74fa9e6b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Parse-SDK-iOS-OSX Chnagelog + +### master + +* Fixes NSInternalInconsistencyException handling starting Bolts 1.9.0 by emitting soft NSErrors +* Fixes issue affecting public getter/setters in PFACL's in Swift (#1083) +* Prevent deadlocks when saving objects with cicrular references (#916) +* Prevent deadlocks when running fetchAll with circluar references (#1184) +* Adds NSNotification when an invalid session token is encountered + diff --git a/Gemfile b/Gemfile index efe553305..4ff79a630 100644 --- a/Gemfile +++ b/Gemfile @@ -7,4 +7,3 @@ gem 'xcpretty' gem 'xcodeproj' gem 'cocoapods' gem 'jazzy', '~> 0.9.0' - diff --git a/Parse.podspec b/Parse.podspec index cec880ea9..2944a05fb 100644 --- a/Parse.podspec +++ b/Parse.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Parse' - s.version = '1.16.0' + s.version = '1.17.0-alpha.5' s.license = { :type => 'BSD', :file => 'LICENSE' } s.homepage = 'http://parseplatform.org/' s.summary = 'A library that gives you access to the powerful Parse cloud platform from your iOS/OS X/watchOS/tvOS app.' diff --git a/Parse/Parse.xcodeproj/project.pbxproj b/Parse/Parse.xcodeproj/project.pbxproj index 2a3ca472a..d1cb37d86 100644 --- a/Parse/Parse.xcodeproj/project.pbxproj +++ b/Parse/Parse.xcodeproj/project.pbxproj @@ -6826,6 +6826,9 @@ LastSwiftMigration = 0900; TestTargetID = 4AE33A0A1F5451AD0088DCA0; }; + 81C09F501AF97A490043B49C = { + LastSwiftMigration = 0920; + }; 81C3821B19CCA89E0066284A = { CreatedOnToolsVersion = 6.0.1; LastSwiftMigration = 0830; @@ -8588,6 +8591,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = F55ABB5A1B4F39DA00A0ECD5 /* ParseUnitTests-macOS.xcconfig */; buildSettings = { + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -8595,6 +8599,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = F55ABB5A1B4F39DA00A0ECD5 /* ParseUnitTests-macOS.xcconfig */; buildSettings = { + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Parse/Parse/Internal/ACL/PFACLPrivate.h b/Parse/Parse/Internal/ACL/PFACLPrivate.h index 97d16251d..67cb90830 100644 --- a/Parse/Parse/Internal/ACL/PFACLPrivate.h +++ b/Parse/Parse/Internal/ACL/PFACLPrivate.h @@ -20,7 +20,7 @@ /* Gets the encoded format for an ACL. */ -- (NSDictionary *)encodeIntoDictionary; +- (NSDictionary *)encodeIntoDictionary:(NSError **)error; /* Creates a new ACL object from an existing dictionary. diff --git a/Parse/Parse/Internal/Analytics/Controller/PFAnalyticsController.m b/Parse/Parse/Internal/Analytics/Controller/PFAnalyticsController.m index dc24b2ad6..3be7086aa 100644 --- a/Parse/Parse/Internal/Analytics/Controller/PFAnalyticsController.m +++ b/Parse/Parse/Internal/Analytics/Controller/PFAnalyticsController.m @@ -77,7 +77,11 @@ + (instancetype)controllerWithDataSource:(id)dataSour @weakify(self); return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ @strongify(self); - NSDictionary *encodedDimensions = [[PFNoObjectEncoder objectEncoder] encodeObject:dimensions]; + NSError *error; + NSDictionary *encodedDimensions = [[PFNoObjectEncoder objectEncoder] encodeObject:dimensions error:&error]; + if (encodedDimensions == nil) { + return [BFTask taskWithError:error]; + } PFRESTCommand *command = [PFRESTAnalyticsCommand trackEventCommandWithEventName:name dimensions:encodedDimensions sessionToken:sessionToken]; diff --git a/Parse/Parse/Internal/CloudCode/PFCloudCodeController.m b/Parse/Parse/Internal/CloudCode/PFCloudCodeController.m index be3db1ac3..96f31be20 100644 --- a/Parse/Parse/Internal/CloudCode/PFCloudCodeController.m +++ b/Parse/Parse/Internal/CloudCode/PFCloudCodeController.m @@ -47,10 +47,14 @@ - (BFTask *)callCloudCodeFunctionAsync:(NSString *)functionName @weakify(self); return [[[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ @strongify(self); - NSDictionary *encodedParameters = [[PFNoObjectEncoder objectEncoder] encodeObject:parameters]; + NSError *error; + NSDictionary *encodedParameters = [[PFNoObjectEncoder objectEncoder] encodeObject:parameters error:&error]; + PFPreconditionReturnFailedTask(encodedParameters, error); PFRESTCloudCommand *command = [PFRESTCloudCommand commandForFunction:functionName withParameters:encodedParameters - sessionToken:sessionToken]; + sessionToken:sessionToken + error:&error]; + PFPreconditionReturnFailedTask(command, error); return [self.dataSource.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithSuccessBlock:^id(BFTask *task) { return ((PFCommandResult *)(task.result)).result[@"result"]; diff --git a/Parse/Parse/Internal/Commands/CommandRunner/URLRequestConstructor/PFCommandURLRequestConstructor.m b/Parse/Parse/Internal/Commands/CommandRunner/URLRequestConstructor/PFCommandURLRequestConstructor.m index 4ae60fab0..c95fd65da 100644 --- a/Parse/Parse/Internal/Commands/CommandRunner/URLRequestConstructor/PFCommandURLRequestConstructor.m +++ b/Parse/Parse/Internal/Commands/CommandRunner/URLRequestConstructor/PFCommandURLRequestConstructor.m @@ -71,7 +71,9 @@ + (instancetype)constructorWithDataSource:(id*task) { return [_session performDataURLRequestAsync:task.result forCommand:command cancellationToken:cancellationToken]; }]; @@ -168,7 +170,9 @@ - (void)dealloc { return [self _performCommandRunningBlock:^id { @strongify(self); - [command resolveLocalIds]; + NSError *error; + BOOL success = [command resolveLocalIds:&error]; + PFPreconditionReturnFailedTask(success, error); return [[self.requestConstructor getFileUploadURLRequestAsyncForCommand:command withContentType:contentType contentSourceFilePath:sourceFilePath] continueWithSuccessBlock:^id(BFTask *task) { diff --git a/Parse/Parse/Internal/Commands/PFRESTAnalyticsCommand.m b/Parse/Parse/Internal/Commands/PFRESTAnalyticsCommand.m index 6badd350e..810b2e482 100644 --- a/Parse/Parse/Internal/Commands/PFRESTAnalyticsCommand.m +++ b/Parse/Parse/Internal/Commands/PFRESTAnalyticsCommand.m @@ -51,11 +51,12 @@ + (instancetype)_trackEventCommandWithEventName:(NSString *)eventName if (!dictionary[@"at"]) { dictionary[@"at"] = [NSDate date]; } - + // TODO: flovilmart do not swallow error here return [self commandWithHTTPPath:httpPath httpMethod:PFHTTPRequestMethodPOST parameters:dictionary - sessionToken:sessionToken]; + sessionToken:sessionToken + error:nil]; } @end diff --git a/Parse/Parse/Internal/Commands/PFRESTCloudCommand.h b/Parse/Parse/Internal/Commands/PFRESTCloudCommand.h index b15bf7a9d..6c6908849 100644 --- a/Parse/Parse/Internal/Commands/PFRESTCloudCommand.h +++ b/Parse/Parse/Internal/Commands/PFRESTCloudCommand.h @@ -15,7 +15,8 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)commandForFunction:(NSString *)function withParameters:(nullable NSDictionary *)parameters - sessionToken:(nullable NSString *)sessionToken; + sessionToken:(nullable NSString *)sessionToken + error:(NSError **)error; @end diff --git a/Parse/Parse/Internal/Commands/PFRESTCloudCommand.m b/Parse/Parse/Internal/Commands/PFRESTCloudCommand.m index 5bd85a986..fd81e5926 100644 --- a/Parse/Parse/Internal/Commands/PFRESTCloudCommand.m +++ b/Parse/Parse/Internal/Commands/PFRESTCloudCommand.m @@ -16,12 +16,14 @@ @implementation PFRESTCloudCommand + (instancetype)commandForFunction:(NSString *)function withParameters:(NSDictionary *)parameters - sessionToken:(NSString *)sessionToken { + sessionToken:(NSString *)sessionToken + error:(NSError **)error { NSString *path = [NSString stringWithFormat:@"functions/%@", function]; return [self commandWithHTTPPath:path httpMethod:PFHTTPRequestMethodPOST parameters:parameters - sessionToken:sessionToken]; + sessionToken:sessionToken + error:error]; } @end diff --git a/Parse/Parse/Internal/Commands/PFRESTCommand.h b/Parse/Parse/Internal/Commands/PFRESTCommand.h index d7328cf35..1064b6b24 100644 --- a/Parse/Parse/Internal/Commands/PFRESTCommand.h +++ b/Parse/Parse/Internal/Commands/PFRESTCommand.h @@ -32,13 +32,15 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)commandWithHTTPPath:(NSString *)path httpMethod:(NSString *)httpMethod parameters:(nullable NSDictionary *)parameters - sessionToken:(nullable NSString *)sessionToken; + sessionToken:(nullable NSString *)sessionToken + error:(NSError **)error; + (instancetype)commandWithHTTPPath:(NSString *)path httpMethod:(NSString *)httpMethod parameters:(nullable NSDictionary *)parameters operationSetUUID:(nullable NSString *)operationSetIdentifier - sessionToken:(nullable NSString *)sessionToken; + sessionToken:(nullable NSString *)sessionToken + error:(NSError **)error; @end diff --git a/Parse/Parse/Internal/Commands/PFRESTCommand.m b/Parse/Parse/Internal/Commands/PFRESTCommand.m index 41464a1ed..2e7da93fa 100644 --- a/Parse/Parse/Internal/Commands/PFRESTCommand.m +++ b/Parse/Parse/Internal/Commands/PFRESTCommand.m @@ -43,19 +43,22 @@ @implementation PFRESTCommand + (instancetype)commandWithHTTPPath:(NSString *)path httpMethod:(NSString *)httpMethod parameters:(NSDictionary *)parameters - sessionToken:(NSString *)sessionToken { + sessionToken:(NSString *)sessionToken + error:(NSError **) error { return [self commandWithHTTPPath:path httpMethod:httpMethod parameters:parameters operationSetUUID:nil - sessionToken:sessionToken]; + sessionToken:sessionToken + error:error]; } + (instancetype)commandWithHTTPPath:(NSString *)path httpMethod:(NSString *)httpMethod parameters:(NSDictionary *)parameters operationSetUUID:(NSString *)operationSetIdentifier - sessionToken:(NSString *)sessionToken { + sessionToken:(NSString *)sessionToken + error:(NSError **)error { PFRESTCommand *command = [[self alloc] init]; command.httpPath = path; command.httpMethod = httpMethod; @@ -105,12 +108,13 @@ + (instancetype)commandFromDictionaryRepresentation:(NSDictionary *)dictionary { PFRESTCommand *command = [self commandWithHTTPPath:dictionary[PFRESTCommandHTTPPathEncodingKey] httpMethod:dictionary[PFRESTCommandHTTPMethodEncodingKey] parameters:dictionary[PFRESTCommandParametersEncodingKey] - sessionToken:dictionary[PFRESTCommandSessionTokenEncodingKey]]; + sessionToken:dictionary[PFRESTCommandSessionTokenEncodingKey] + error:nil]; command.localId = dictionary[PFRESTCommandLocalIdEncodingKey]; return command; } -- (NSDictionary *)dictionaryRepresentation { +- (NSDictionary *)dictionaryRepresentation:(NSError **)error { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; if (self.httpPath) { dictionary[PFRESTCommandHTTPPathEncodingKey] = self.httpPath; @@ -119,7 +123,10 @@ - (NSDictionary *)dictionaryRepresentation { dictionary[PFRESTCommandHTTPMethodEncodingKey] = self.httpMethod; } if (self.parameters) { - NSDictionary *parameters = [[PFPointerOrLocalIdObjectEncoder objectEncoder] encodeObject:self.parameters]; + NSDictionary *parameters = [[PFPointerOrLocalIdObjectEncoder objectEncoder] encodeObject:self.parameters error:error]; + if (!parameters) { + return nil; + } dictionary[PFRESTCommandParametersEncodingKey] = parameters; } if (self.sessionToken) { @@ -135,6 +142,7 @@ + (BOOL)isValidDictionaryRepresentation:(NSDictionary *)dictionary { return dictionary[PFRESTCommandHTTPPathEncodingKey] != nil; } + #pragma mark Local Identifiers /** @@ -166,66 +174,91 @@ - (void)maybeChangeServerOperation { } } -+ (BOOL)forEachLocalIdIn:(id)object doBlock:(BOOL(^)(PFObject *pointer))block { - __block BOOL modified = NO; ++ (BOOL)forEachLocalIdIn:(id)object + doBlock:(BOOL(^)(PFObject *pointer, BOOL *modified, NSError **error))block + modified:(BOOL *)modified error:(NSError **)error { // If this is a Pointer with a local id, try to resolve it. if ([object isKindOfClass:[PFObject class]] && !((PFObject *)object).objectId) { - return block(object); + __block BOOL blockModified = NO; + BOOL success = block(object, &blockModified, error); + if (blockModified) { + *modified = YES; + } + return success; } if ([object isKindOfClass:[NSDictionary class]]) { + __block NSError *localError; + __block BOOL hasFailed = NO; [object enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { - if ([[self class] forEachLocalIdIn:obj doBlock:block]) { - modified = YES; + if (![[self class] forEachLocalIdIn:obj doBlock:block modified:modified error:&localError]) { + *stop = YES; + hasFailed = YES; } }]; + if (hasFailed && error) { + *error = localError; + return NO; + } } else if ([object isKindOfClass:[NSArray class]]) { for (id value in object) { - if ([[self class] forEachLocalIdIn:value doBlock:block]) { - modified = YES; + if (![[self class] forEachLocalIdIn:value doBlock:block modified:modified error:error]) { + return NO; } } } else if ([object isKindOfClass:[PFAddOperation class]]) { for (id value in ((PFAddOperation *)object).objects) { - if ([[self class] forEachLocalIdIn:value doBlock:block]) { - modified = YES; + if (![[self class] forEachLocalIdIn:value doBlock:block modified:modified error:error]) { + return NO; } } } else if ([object isKindOfClass:[PFAddUniqueOperation class]]) { for (id value in ((PFAddUniqueOperation *)object).objects) { - if ([[self class] forEachLocalIdIn:value doBlock:block]) { - modified = YES; + if (![[self class] forEachLocalIdIn:value doBlock:block modified:modified error:error]) { + return NO; } } } else if ([object isKindOfClass:[PFRemoveOperation class]]) { for (id value in ((PFRemoveOperation *)object).objects) { - if ([[self class] forEachLocalIdIn:value doBlock:block]) { - modified = YES; + if (![[self class] forEachLocalIdIn:value doBlock:block modified:modified error:error]) { + return NO; } } } - - return modified; + return YES; } -- (void)forEachLocalId:(BOOL(^)(PFObject *pointer))block { +- (BOOL)forEachLocalId:(BOOL(^)(PFObject *pointer, BOOL *modified, NSError **error))block error:(NSError **)error { NSDictionary *data = [[PFDecoder objectDecoder] decodeObject:self.parameters]; if (!data) { - return; + return YES; } - - if ([[self class] forEachLocalIdIn:data doBlock:block]) { - self.parameters = [[PFPointerOrLocalIdObjectEncoder objectEncoder] encodeObject:data]; + BOOL modified = NO; + if ([[self class] forEachLocalIdIn:data doBlock:block modified:&modified error:error]) { + self.parameters = [[PFPointerOrLocalIdObjectEncoder objectEncoder] encodeObject:data error:error]; + if (self.parameters && !(error && *error)) { + return YES; + } } + return NO; } -- (void)resolveLocalIds { - [self forEachLocalId:^(PFObject *pointer) { - [pointer resolveLocalId]; - return YES; - }]; +- (BOOL)resolveLocalIds:(NSError * __autoreleasing *)error { + BOOL paramEncodingSucceeded = [self forEachLocalId:^(PFObject *pointer, BOOL *modified, NSError **blockError) { + NSError *localError; + BOOL success = [pointer resolveLocalId:&localError]; + *modified = YES; + if (!success && localError) { + *blockError = localError; + } + return success; + } error: error]; + if (!paramEncodingSucceeded && *error) { + return NO; + } [self maybeChangeServerOperation]; + return YES; } @end diff --git a/Parse/Parse/Internal/Commands/PFRESTConfigCommand.m b/Parse/Parse/Internal/Commands/PFRESTConfigCommand.m index 6709990a2..47760fa5b 100644 --- a/Parse/Parse/Internal/Commands/PFRESTConfigCommand.m +++ b/Parse/Parse/Internal/Commands/PFRESTConfigCommand.m @@ -18,7 +18,8 @@ + (instancetype)configFetchCommandWithSessionToken:(NSString *)sessionToken { return [self commandWithHTTPPath:@"config" httpMethod:PFHTTPRequestMethodGET parameters:nil - sessionToken:sessionToken]; + sessionToken:sessionToken + error:nil]; } + (instancetype)configUpdateCommandWithConfigParameters:(NSDictionary *)parameters @@ -27,7 +28,8 @@ + (instancetype)configUpdateCommandWithConfigParameters:(NSDictionary *)paramete return [self commandWithHTTPPath:@"config" httpMethod:PFHTTPRequestMethodPUT parameters:commandParameters - sessionToken:sessionToken]; + sessionToken:sessionToken + error:nil]; } @end diff --git a/Parse/Parse/Internal/Commands/PFRESTFileCommand.m b/Parse/Parse/Internal/Commands/PFRESTFileCommand.m index 1de7cc9e3..368cc0d79 100644 --- a/Parse/Parse/Internal/Commands/PFRESTFileCommand.m +++ b/Parse/Parse/Internal/Commands/PFRESTFileCommand.m @@ -23,7 +23,8 @@ + (instancetype)uploadCommandForFileWithName:(NSString *)fileName return [self commandWithHTTPPath:httpPath httpMethod:PFHTTPRequestMethodPOST parameters:nil - sessionToken:sessionToken]; + sessionToken:sessionToken + error:nil]; } @end diff --git a/Parse/Parse/Internal/Commands/PFRESTObjectBatchCommand.h b/Parse/Parse/Internal/Commands/PFRESTObjectBatchCommand.h index 258cf22c2..34b10e591 100644 --- a/Parse/Parse/Internal/Commands/PFRESTObjectBatchCommand.h +++ b/Parse/Parse/Internal/Commands/PFRESTObjectBatchCommand.h @@ -19,7 +19,8 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)batchCommandWithCommands:(NSArray *)commands sessionToken:(nullable NSString *)sessionToken - serverURL:(NSURL *)serverURL; + serverURL:(NSURL *)serverURL + error:(NSError **)error; @end diff --git a/Parse/Parse/Internal/Commands/PFRESTObjectBatchCommand.m b/Parse/Parse/Internal/Commands/PFRESTObjectBatchCommand.m index 48d1a2da8..013752e2a 100644 --- a/Parse/Parse/Internal/Commands/PFRESTObjectBatchCommand.m +++ b/Parse/Parse/Internal/Commands/PFRESTObjectBatchCommand.m @@ -19,7 +19,8 @@ @implementation PFRESTObjectBatchCommand + (nonnull instancetype)batchCommandWithCommands:(nonnull NSArray *)commands sessionToken:(nullable NSString *)sessionToken - serverURL:(nonnull NSURL *)serverURL { + serverURL:(nonnull NSURL *)serverURL + error:(NSError **)error { PFParameterAssert(commands.count <= PFRESTObjectBatchCommandSubcommandsLimit, @"Max of %d commands are allowed in a single batch command", (int)PFRESTObjectBatchCommandSubcommandsLimit); @@ -40,7 +41,8 @@ + (nonnull instancetype)batchCommandWithCommands:(nonnull NSArray 0) { NSMutableDictionary *whereData = [[NSMutableDictionary alloc] init]; [conditions enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { @@ -152,7 +166,13 @@ + (NSDictionary *)findCommandParametersWithOrder:(NSString *)order limit:subquery.state.limit skip:subquery.state.skip extraOptions:nil - tracingEnabled:NO]; + tracingEnabled:NO + error:&encodingError]; + if (!queryDict && encodingError) { + *stop = true; + encodingFailed = true; + return; + } queryDict = queryDict[@"where"]; if (queryDict.count > 0) { @@ -163,42 +183,76 @@ + (NSDictionary *)findCommandParametersWithOrder:(NSString *)order } whereData[key] = newArray; } else { - id object = [self _encodeSubqueryIfNeeded:obj]; - whereData[key] = [[PFPointerObjectEncoder objectEncoder] encodeObject:object]; + id object = [self _encodeSubqueryIfNeeded:obj error:&encodingError]; + if (!object && encodingError) { + *stop = true; + encodingFailed = true; + return; + } + id pointer = [[PFPointerObjectEncoder objectEncoder] encodeObject:object error:&encodingError]; + if (!pointer && encodingError) { + *stop = true; + encodingFailed = true; + return; + } + whereData[key] = pointer; } }]; parameters[@"where"] = whereData; } + if (encodingFailed && encodingError) { + *error = encodingError; + return nil; + } return parameters; } -+ (id)_encodeSubqueryIfNeeded:(id)object { ++ (nullable id)_encodeSubqueryIfNeeded:(id)object error:(NSError * __autoreleasing *)error { if (![object isKindOfClass:[NSDictionary class]]) { return object; } NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithCapacity:[object count]]; + __block BOOL encodingFailed = NO; + __block NSError *encodingError = nil; [object enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if ([obj isKindOfClass:[PFQuery class]]) { PFQuery *subquery = (PFQuery *)obj; - NSMutableDictionary *subqueryParameters = [[self findCommandParametersWithOrder:subquery.state.sortOrderString - conditions:subquery.state.conditions - selectedKeys:subquery.state.selectedKeys - includedKeys:subquery.state.includedKeys - limit:subquery.state.limit - skip:subquery.state.skip - extraOptions:subquery.state.extraOptions - tracingEnabled:NO] mutableCopy]; + NSDictionary *command = [self findCommandParametersWithOrder:subquery.state.sortOrderString + conditions:subquery.state.conditions + selectedKeys:subquery.state.selectedKeys + includedKeys:subquery.state.includedKeys + limit:subquery.state.limit + skip:subquery.state.skip + extraOptions:subquery.state.extraOptions + tracingEnabled:NO + error:&encodingError]; + if (!command && encodingError) { + encodingFailed = YES; + *stop = YES; + return; + } + NSMutableDictionary *subqueryParameters = [command mutableCopy]; subqueryParameters[@"className"] = subquery.parseClassName; obj = subqueryParameters; } else if ([obj isKindOfClass:[NSDictionary class]]) { - obj = [self _encodeSubqueryIfNeeded:obj]; + obj = [self _encodeSubqueryIfNeeded:obj error:&encodingError]; + if (!obj && encodingError) { + encodingFailed = YES; + *stop = YES; + return; + } } - parameters[key] = obj; }]; + if (encodingFailed) { + if (error && encodingError) { + *error = encodingError; + } + return nil; + } return parameters; } diff --git a/Parse/Parse/Internal/Commands/PFRESTSessionCommand.m b/Parse/Parse/Internal/Commands/PFRESTSessionCommand.m index c38bd1c6d..2ef362394 100644 --- a/Parse/Parse/Internal/Commands/PFRESTSessionCommand.m +++ b/Parse/Parse/Internal/Commands/PFRESTSessionCommand.m @@ -17,7 +17,8 @@ + (instancetype)getCurrentSessionCommandWithSessionToken:(nullable NSString *)se return [self commandWithHTTPPath:@"sessions/me" httpMethod:PFHTTPRequestMethodGET parameters:nil - sessionToken:sessionToken]; + sessionToken:sessionToken + error:nil]; } @end diff --git a/Parse/Parse/Internal/Commands/PFRESTUserCommand.h b/Parse/Parse/Internal/Commands/PFRESTUserCommand.h index 92758d930..8e2eb0e1c 100644 --- a/Parse/Parse/Internal/Commands/PFRESTUserCommand.h +++ b/Parse/Parse/Internal/Commands/PFRESTUserCommand.h @@ -21,13 +21,16 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)logInUserCommandWithUsername:(NSString *)username password:(NSString *)password - revocableSession:(BOOL)revocableSessionEnabled; + revocableSession:(BOOL)revocableSessionEnabled + error:(NSError **)error; + (instancetype)serviceLoginUserCommandWithAuthenticationType:(NSString *)authenticationType authenticationData:(NSDictionary *)authenticationData - revocableSession:(BOOL)revocableSessionEnabled; + revocableSession:(BOOL)revocableSessionEnabled + error:(NSError **)error; + (instancetype)serviceLoginUserCommandWithParameters:(NSDictionary *)parameters revocableSession:(BOOL)revocableSessionEnabled - sessionToken:(nullable NSString *)sessionToken; + sessionToken:(nullable NSString *)sessionToken + error:(NSError **)error; ///-------------------------------------- #pragma mark - Sign Up @@ -35,21 +38,22 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)signUpUserCommandWithParameters:(NSDictionary *)parameters revocableSession:(BOOL)revocableSessionEnabled - sessionToken:(nullable NSString *)sessionToken; + sessionToken:(nullable NSString *)sessionToken + error:(NSError **)error; ///-------------------------------------- #pragma mark - Current User ///-------------------------------------- -+ (instancetype)getCurrentUserCommandWithSessionToken:(NSString *)sessionToken; -+ (instancetype)upgradeToRevocableSessionCommandWithSessionToken:(NSString *)sessionToken; -+ (instancetype)logOutUserCommandWithSessionToken:(NSString *)sessionToken; ++ (instancetype)getCurrentUserCommandWithSessionToken:(NSString *)sessionToken error:(NSError **)error; ++ (instancetype)upgradeToRevocableSessionCommandWithSessionToken:(NSString *)sessionToken error:(NSError **)error; ++ (instancetype)logOutUserCommandWithSessionToken:(NSString *)sessionToken error:(NSError **)error; ///-------------------------------------- #pragma mark - Password Rest ///-------------------------------------- -+ (instancetype)resetPasswordCommandForUserWithEmail:(NSString *)email; ++ (instancetype)resetPasswordCommandForUserWithEmail:(NSString *)email error:(NSError **)error; @end diff --git a/Parse/Parse/Internal/Commands/PFRESTUserCommand.m b/Parse/Parse/Internal/Commands/PFRESTUserCommand.m index 190a5d502..b5126c447 100644 --- a/Parse/Parse/Internal/Commands/PFRESTUserCommand.m +++ b/Parse/Parse/Internal/Commands/PFRESTUserCommand.m @@ -31,11 +31,14 @@ + (instancetype)_commandWithHTTPPath:(NSString *)path httpMethod:(NSString *)httpMethod parameters:(NSDictionary *)parameters sessionToken:(NSString *)sessionToken - revocableSession:(BOOL)revocableSessionEnabled { + revocableSession:(BOOL)revocableSessionEnabled + error:(NSError **) error { PFRESTUserCommand *command = [self commandWithHTTPPath:path httpMethod:httpMethod parameters:parameters - sessionToken:sessionToken]; + sessionToken:sessionToken + error:error]; + PFPreconditionBailOnError(command, error, nil); if (revocableSessionEnabled) { command.additionalRequestHeaders = @{ PFRESTUserCommandRevocableSessionHeader : PFRESTUserCommandRevocableSessionHeaderEnabledValue}; @@ -50,33 +53,39 @@ + (instancetype)_commandWithHTTPPath:(NSString *)path + (instancetype)logInUserCommandWithUsername:(NSString *)username password:(NSString *)password - revocableSession:(BOOL)revocableSessionEnabled { + revocableSession:(BOOL)revocableSessionEnabled + error:(NSError **) error { NSDictionary *parameters = @{ @"username" : username, @"password" : password }; return [self _commandWithHTTPPath:@"login" httpMethod:PFHTTPRequestMethodGET parameters:parameters sessionToken:nil - revocableSession:revocableSessionEnabled]; + revocableSession:revocableSessionEnabled + error:error]; } + (instancetype)serviceLoginUserCommandWithAuthenticationType:(NSString *)authenticationType authenticationData:(NSDictionary *)authenticationData - revocableSession:(BOOL)revocableSessionEnabled { + revocableSession:(BOOL)revocableSessionEnabled + error:(NSError **)error { NSDictionary *parameters = @{ @"authData" : @{ authenticationType : authenticationData } }; return [self serviceLoginUserCommandWithParameters:parameters revocableSession:revocableSessionEnabled - sessionToken:nil]; + sessionToken:nil + error:error]; } + (instancetype)serviceLoginUserCommandWithParameters:(NSDictionary *)parameters revocableSession:(BOOL)revocableSessionEnabled - sessionToken:(NSString *)sessionToken { + sessionToken:(NSString *)sessionToken + error:(NSError **)error { return [self _commandWithHTTPPath:@"users" httpMethod:PFHTTPRequestMethodPOST parameters:parameters sessionToken:sessionToken - revocableSession:revocableSessionEnabled]; + revocableSession:revocableSessionEnabled + error:error]; } ///-------------------------------------- @@ -85,48 +94,54 @@ + (instancetype)serviceLoginUserCommandWithParameters:(NSDictionary *)parameters + (instancetype)signUpUserCommandWithParameters:(NSDictionary *)parameters revocableSession:(BOOL)revocableSessionEnabled - sessionToken:(NSString *)sessionToken { + sessionToken:(NSString *)sessionToken + error:(NSError **)error { return [self _commandWithHTTPPath:@"users" httpMethod:PFHTTPRequestMethodPOST parameters:parameters sessionToken:sessionToken - revocableSession:revocableSessionEnabled]; + revocableSession:revocableSessionEnabled + error:error]; } ///-------------------------------------- #pragma mark - Current User ///-------------------------------------- -+ (instancetype)getCurrentUserCommandWithSessionToken:(NSString *)sessionToken { ++ (instancetype)getCurrentUserCommandWithSessionToken:(NSString *)sessionToken error:(NSError **)error { return [self commandWithHTTPPath:@"users/me" httpMethod:PFHTTPRequestMethodGET parameters:nil - sessionToken:sessionToken]; + sessionToken:sessionToken + error:error]; } -+ (instancetype)upgradeToRevocableSessionCommandWithSessionToken:(NSString *)sessionToken { ++ (instancetype)upgradeToRevocableSessionCommandWithSessionToken:(NSString *)sessionToken error:(NSError **)error { return [self commandWithHTTPPath:@"upgradeToRevocableSession" httpMethod:PFHTTPRequestMethodPOST parameters:nil - sessionToken:sessionToken]; + sessionToken:sessionToken + error:error]; } -+ (instancetype)logOutUserCommandWithSessionToken:(NSString *)sessionToken { ++ (instancetype)logOutUserCommandWithSessionToken:(NSString *)sessionToken error:(NSError **)error { return [self commandWithHTTPPath:@"logout" httpMethod:PFHTTPRequestMethodPOST parameters:nil - sessionToken:sessionToken]; + sessionToken:sessionToken + error:error]; } ///-------------------------------------- #pragma mark - Additional User Commands ///-------------------------------------- -+ (instancetype)resetPasswordCommandForUserWithEmail:(NSString *)email { ++ (instancetype)resetPasswordCommandForUserWithEmail:(NSString *)email error:(NSError **)error { return [self commandWithHTTPPath:@"requestPasswordReset" httpMethod:PFHTTPRequestMethodPOST parameters:@{ @"email" : email } - sessionToken:nil]; + sessionToken:nil + error:error]; } @end diff --git a/Parse/Parse/Internal/Config/Controller/PFCurrentConfigController.m b/Parse/Parse/Internal/Config/Controller/PFCurrentConfigController.m index e03e134e9..e7e4bf400 100644 --- a/Parse/Parse/Internal/Config/Controller/PFCurrentConfigController.m +++ b/Parse/Parse/Internal/Config/Controller/PFCurrentConfigController.m @@ -72,7 +72,11 @@ - (BFTask *)setCurrentConfigAsync:(PFConfig *)config { _currentConfig = config; NSDictionary *configParameters = @{ PFConfigParametersRESTKey : (config.parametersDictionary ?: @{}) }; - id encodedObject = [[PFPointerObjectEncoder objectEncoder] encodeObject:configParameters]; + NSError *error; + id encodedObject = [[PFPointerObjectEncoder objectEncoder] encodeObject:configParameters error:&error]; + if (!encodedObject) { + return [BFTask taskWithError:error]; + } NSData *jsonData = [PFJSONSerialization dataFromJSONObject:encodedObject]; return [[self _getPersistenceGroupAsync] continueWithSuccessBlock:^id(BFTask> *task) { return [task.result setDataAsync:jsonData forKey:PFConfigCurrentConfigFileName_]; diff --git a/Parse/Parse/Internal/FieldOperation/PFFieldOperation.h b/Parse/Parse/Internal/FieldOperation/PFFieldOperation.h index 967b57177..4e529dc97 100644 --- a/Parse/Parse/Internal/FieldOperation/PFFieldOperation.h +++ b/Parse/Parse/Internal/FieldOperation/PFFieldOperation.h @@ -33,7 +33,7 @@ @param objectEncoder encoder that will be used to encode the object. @return An object to be jsonified. */ -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder; +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error; /** Returns a field operation that is composed of a previous operation followed by diff --git a/Parse/Parse/Internal/FieldOperation/PFFieldOperation.m b/Parse/Parse/Internal/FieldOperation/PFFieldOperation.m index 116c6f668..ebf1389c3 100644 --- a/Parse/Parse/Internal/FieldOperation/PFFieldOperation.m +++ b/Parse/Parse/Internal/FieldOperation/PFFieldOperation.m @@ -24,7 +24,7 @@ // PFFieldOperation and its subclasses encapsulate operations that can be done on a field. @implementation PFFieldOperation -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder { +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { PFConsistencyAssertionFailure(@"Operation is invalid."); return nil; } @@ -69,8 +69,8 @@ - (NSString *)description { return [NSString stringWithFormat:@"set to %@", self.value]; } -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder { - return [objectEncoder encodeObject:self.value]; +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { + return [objectEncoder encodeObject:self.value error:error]; } - (PFSetOperation *)mergeWithPrevious:(PFFieldOperation *)previous { @@ -93,7 +93,7 @@ - (NSString *)description { return @"delete"; } -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder { +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { return @{ @"__op" : @"Delete" }; } @@ -134,7 +134,7 @@ - (NSString *)description { return [NSString stringWithFormat:@"increment by %@", self.amount]; } -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder { +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { return @{ @"__op" : @"Increment", @"amount" : self.amount }; } @@ -191,8 +191,9 @@ - (NSString *)description { return [NSString stringWithFormat:@"add %@", self.objects]; } -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder { - NSMutableArray *encodedObjects = [objectEncoder encodeObject:self.objects]; +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { + NSMutableArray *encodedObjects = [objectEncoder encodeObject:self.objects error: error]; + PFPreconditionBailOnError(encodedObjects, error, nil); return @{ @"__op" : @"Add", @"objects" : encodedObjects }; } @@ -251,8 +252,9 @@ - (NSString *)description { return [NSString stringWithFormat:@"addToSet %@", self.objects]; } -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder { - NSMutableArray *encodedObjects = [objectEncoder encodeObject:self.objects]; +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { + NSMutableArray *encodedObjects = [objectEncoder encodeObject:self.objects error:error]; + PFPreconditionBailOnError(encodedObjects, error, nil); return @{ @"__op" : @"AddUnique", @"objects" : encodedObjects }; } @@ -326,8 +328,9 @@ - (NSString *)description { return [NSString stringWithFormat:@"remove %@", self.objects]; } -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder { - NSMutableArray *encodedObjects = [objectEncoder encodeObject:self.objects]; +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { + NSMutableArray *encodedObjects = [objectEncoder encodeObject:self.objects error:error]; + PFPreconditionBailOnError(encodedObjects, error, nil); return @{ @"__op" : @"Remove", @"objects" : encodedObjects }; } @@ -439,25 +442,32 @@ - (NSString *)description { self.relationsToRemove]; } -- (NSArray *)_convertToArrayInSet:(NSSet *)set withObjectEncoder:(PFEncoder *)objectEncoder { +- (NSArray *)_convertToArrayInSet:(NSSet *)set withObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { NSMutableArray *array = [NSMutableArray arrayWithCapacity:set.count]; for (PFObject *object in set) { - id encodedDict = [objectEncoder encodeObject:object]; + id encodedDict = [objectEncoder encodeObject:object error:error]; + PFPreconditionBailOnError(encodedDict, error, nil); [array addObject:encodedDict]; } return array; } -- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder { +- (id)encodeWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { NSDictionary *addDict = nil; NSDictionary *removeDict = nil; if (self.relationsToAdd.count > 0) { - NSArray *array = [self _convertToArrayInSet:self.relationsToAdd withObjectEncoder:objectEncoder]; + NSArray *array = [self _convertToArrayInSet:self.relationsToAdd withObjectEncoder:objectEncoder error:error]; + if (!array) { + return nil; + } addDict = @{ @"__op" : @"AddRelation", @"objects" : array }; } if (self.relationsToRemove.count > 0) { - NSArray *array = [self _convertToArrayInSet:self.relationsToRemove withObjectEncoder:objectEncoder]; + NSArray *array = [self _convertToArrayInSet:self.relationsToRemove withObjectEncoder:objectEncoder error:error]; + if (!array) { + return nil; + } removeDict = @{ @"__op" : @"RemoveRelation", @"objects" : array }; } diff --git a/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m b/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m index 9eddc757c..33d6617ef 100644 --- a/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m +++ b/Parse/Parse/Internal/LocalDataStore/OfflineQueryLogic/PFOfflineQueryLogic.m @@ -121,7 +121,8 @@ - (id)valueForContainer:(id)container // anything like ParseObjects and arrays. if (!(value == nil || [value isKindOfClass:[NSDictionary class]])) { if (depth > 0) { - id restFormat = [[PFPointerObjectEncoder objectEncoder] encodeObject:value]; + // TODO (flovilmart): is it safe to swallow the error here? + id restFormat = [[PFPointerObjectEncoder objectEncoder] encodeObject:value error:nil]; if ([restFormat isKindOfClass:[NSDictionary class]]) { return [self valueForContainer:restFormat key:rest depth:depth + 1]; } diff --git a/Parse/Parse/Internal/LocalDataStore/OfflineStore/PFOfflineStore.m b/Parse/Parse/Internal/LocalDataStore/OfflineStore/PFOfflineStore.m index f34386f6d..f178dd32d 100644 --- a/Parse/Parse/Internal/LocalDataStore/OfflineStore/PFOfflineStore.m +++ b/Parse/Parse/Internal/LocalDataStore/OfflineStore/PFOfflineStore.m @@ -178,7 +178,7 @@ - (instancetype)initWithFileManager:(PFFileManager *)fileManager options:(PFOffl PFOfflineStoreKeyOfJSON, PFOfflineStoreTableOfObjects, PFOfflineStoreKeyOfUUID]; return [database executeQueryAsync:query withArgumentsInArray:@[ uuid ] block:^id(PFSQLiteDatabaseResult *_Nonnull result) { if (![result next]) { - PFConsistencyAssertionFailure(@"Attempted to find non-existent uuid %@. Please report this issue with stack traces and logs.", uuid); + PFPreconditionFailure(@"Attempted to find non-existent uuid %@. Please report this issue with stack traces and logs.", uuid); } return [result stringForColumnIndex:0]; }]; @@ -264,7 +264,10 @@ - (instancetype)initWithFileManager:(PFFileManager *)fileManager options:(PFOffl NSArray *objectValues = offlineObjects.allValues; return [[BFTask taskForCompletionOfAllTasks:objectValues] continueWithSuccessBlock:^id(BFTask *task) { PFDecoder *decoder = [PFOfflineDecoder decoderWithOfflineObjects:offlineObjects]; - [object mergeFromRESTDictionary:parsedJson withDecoder:decoder]; + NSError *error; + if (![object mergeFromRESTDictionary:parsedJson withDecoder:decoder error:&error]) { + return [BFTask taskWithError:error]; + } return nil; }]; }] continueWithBlock:^id(BFTask *task) { @@ -379,7 +382,9 @@ - (instancetype)initWithFileManager:(PFFileManager *)fileManager options:(PFOffl PFOfflineObjectEncoder *encoder = [PFOfflineObjectEncoder objectEncoderWithOfflineStore:self database:database]; // We don't care about operationSetUUIDs here NSArray *operationSetUUIDs = nil; - encoded = [object RESTDictionaryWithObjectEncoder:encoder operationSetUUIDs:&operationSetUUIDs]; + NSError *error; + encoded = [object RESTDictionaryWithObjectEncoder:encoder operationSetUUIDs:&operationSetUUIDs error:&error]; + PFPreconditionReturnFailedTask(encoded, error); return [encoder encodeFinished]; }] continueWithSuccessBlock:^id(BFTask *task) { // Time to actually save the object @@ -616,7 +621,9 @@ - (BFTask *)findAsyncForQueryState:(PFQueryState *)queryState PFOfflineObjectEncoder *encoder = [PFOfflineObjectEncoder objectEncoderWithOfflineStore:self database:database]; NSArray *operationSetUUIDs = nil; - dataDictionary = [object RESTDictionaryWithObjectEncoder:encoder operationSetUUIDs:&operationSetUUIDs]; + NSError *error; + dataDictionary = [object RESTDictionaryWithObjectEncoder:encoder operationSetUUIDs:&operationSetUUIDs error:&error]; + PFPreconditionReturnFailedTask(dataDictionary, error); return [encoder encodeFinished]; }] continueWithSuccessBlock:^id(BFTask *task) { // Put it in database @@ -905,7 +912,7 @@ - (BFTask *)findAsyncForQueryState:(PFQueryState *)queryState __block NSString *objectId = nil; return [[database executeQueryAsync:query withArgumentsInArray:@[ uuid ] block:^id(PFSQLiteDatabaseResult *result) { if (![result next]) { - PFConsistencyAssertionFailure(@"Attempted to find non-existent uuid %@. Please report this issue with stack traces and logs.", uuid); + PFPreconditionFailure(@"Attempted to find non-existent uuid %@. Please report this issue with stack traces and logs.", uuid); } className = [result stringForColumnIndex:0]; @@ -965,6 +972,7 @@ - (BFTask *)findAsyncForQueryState:(PFQueryState *)queryState @synchronized(self.lock) { pointer = [self.UUIDToObjectMap objectForKey:uuid]; if (!pointer) { + PFPreconditionWithTask(parseClassName, @"Unable to get fetch an object without a className %@ %@ %@", uuid, objectId, parseClassName); pointer = [PFObject objectWithoutDataWithClassName:parseClassName objectId:objectId]; // If it doesn't have objectId, we don't really need the UUID, and this simplifies some @@ -1021,7 +1029,8 @@ - (void)updateObjectIdForObject:(PFObject *)object // See if there's already an entry for new objectId. PFObject *existing = [self.classNameAndObjectIdToObjectMap objectForKey:key]; PFConsistencyAssert(existing == nil || existing == object, - @"Attempted to change an objectId to one that's already known to the OfflineStore."); + @"Attempted to change an objectId to one that's already known to the OfflineStore. className: %@ old: %@, new: %@", + className, oldObjectId, newObjectId); // Okay, all clear to add the new reference. [self.classNameAndObjectIdToObjectMap setObject:object forKey:key]; diff --git a/Parse/Parse/Internal/Object/BatchController/PFObjectBatchController.m b/Parse/Parse/Internal/Object/BatchController/PFObjectBatchController.m index bef1681c6..9b2de0fa4 100644 --- a/Parse/Parse/Internal/Object/BatchController/PFObjectBatchController.m +++ b/Parse/Parse/Internal/Object/BatchController/PFObjectBatchController.m @@ -55,7 +55,9 @@ - (BFTask *)fetchObjectsAsync:(NSArray *)objects withSessionToken:(NSString *)se @weakify(self); return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ @strongify(self); - PFRESTCommand *command = [self _fetchCommandForObjects:objects withSessionToken:sessionToken]; + NSError *error; + PFRESTCommand *command = [self _fetchCommandForObjects:objects withSessionToken:sessionToken error:&error]; + PFPreconditionReturnFailedTask(command, error); return [self.dataSource.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithSuccessBlock:^id(BFTask *task) { @@ -65,12 +67,14 @@ - (BFTask *)fetchObjectsAsync:(NSArray *)objects withSessionToken:(NSString *)se }]; } -- (PFRESTCommand *)_fetchCommandForObjects:(NSArray *)objects withSessionToken:(NSString *)sessionToken { +- (PFRESTCommand *)_fetchCommandForObjects:(NSArray *)objects + withSessionToken:(NSString *)sessionToken + error:(NSError **)error { NSArray *objectIds = [objects valueForKey:@keypath(PFObject, objectId)]; PFQuery *query = [PFQuery queryWithClassName:[objects.firstObject parseClassName]]; [query whereKey:@keypath(PFObject, objectId) containedIn:objectIds]; query.limit = objectIds.count; - return [PFRESTQueryCommand findCommandForQueryState:query.state withSessionToken:sessionToken]; + return [PFRESTQueryCommand findCommandForQueryState:query.state withSessionToken:sessionToken error:error]; } - (BFTask *)_processFetchResultAsync:(NSDictionary *)result forObjects:(NSArray *)objects { @@ -117,8 +121,12 @@ - (BFTask *)deleteObjectsAsync:(NSArray *)objects withSessionToken:(NSString *)s id commandRunner = self.dataSource.commandRunner; NSURL *serverURL = commandRunner.serverURL; for (NSArray *batch in objectBatches) { - - PFRESTCommand *command = [self _deleteCommandForObjects:batch withSessionToken:sessionToken serverURL:serverURL]; + NSError *error; + PFRESTCommand *command = [self _deleteCommandForObjects:batch withSessionToken:sessionToken serverURL:serverURL error:&error]; + if (!command) { + [tasks addObject:[BFTask taskWithError:error]]; + continue; + } BFTask *task = [[commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed] continueWithSuccessBlock:^id(BFTask *task) { PFCommandResult *result = task.result; @@ -149,14 +157,15 @@ - (BFTask *)deleteObjectsAsync:(NSArray *)objects withSessionToken:(NSString *)s - (PFRESTCommand *)_deleteCommandForObjects:(NSArray *)objects withSessionToken:(NSString *)sessionToken - serverURL:(NSURL *)serverURL { + serverURL:(NSURL *)serverURL + error:(NSError **)error { NSMutableArray *commands = [NSMutableArray arrayWithCapacity:objects.count]; for (PFObject *object in objects) { PFRESTCommand *deleteCommand = [PFRESTObjectCommand deleteObjectCommandForObjectState:object._state withSessionToken:sessionToken]; [commands addObject:deleteCommand]; } - return [PFRESTObjectBatchCommand batchCommandWithCommands:commands sessionToken:sessionToken serverURL:serverURL]; + return [PFRESTObjectBatchCommand batchCommandWithCommands:commands sessionToken:sessionToken serverURL:serverURL error:error]; } - (BFTask *)_processDeleteResultsAsync:(NSArray *)results forObjects:(NSArray *)objects { diff --git a/Parse/Parse/Internal/Object/Coder/File/PFObjectFileCoder.m b/Parse/Parse/Internal/Object/Coder/File/PFObjectFileCoder.m index 75bc1975e..4baa58be8 100644 --- a/Parse/Parse/Internal/Object/Coder/File/PFObjectFileCoder.m +++ b/Parse/Parse/Internal/Object/Coder/File/PFObjectFileCoder.m @@ -23,7 +23,8 @@ @implementation PFObjectFileCoder + (NSData *)dataFromObject:(PFObject *)object usingEncoder:(PFEncoder *)encoder { NSMutableDictionary *result = [NSMutableDictionary dictionary]; result[@"classname"] = object._state.parseClassName; - result[@"data"] = [object._state dictionaryRepresentationWithObjectEncoder:encoder]; + // TODO (flovilmart): is it safe to swallow error here? + result[@"data"] = [object._state dictionaryRepresentationWithObjectEncoder:encoder error:nil]; return [PFJSONSerialization dataFromJSONObject:result]; } diff --git a/Parse/Parse/Internal/Object/LocalIdStore/PFObjectLocalIdStore.h b/Parse/Parse/Internal/Object/LocalIdStore/PFObjectLocalIdStore.h index 51ebd8d84..83800f4c2 100644 --- a/Parse/Parse/Internal/Object/LocalIdStore/PFObjectLocalIdStore.h +++ b/Parse/Parse/Internal/Object/LocalIdStore/PFObjectLocalIdStore.h @@ -38,10 +38,10 @@ ///-------------------------------------- - (NSString *)createLocalId; -- (void)retainLocalIdOnDisk:(NSString *)localId; -- (void)releaseLocalIdOnDisk:(NSString *)localId; +- (BOOL)retainLocalIdOnDisk:(NSString *)localId error:(NSError **)error; +- (BOOL)releaseLocalIdOnDisk:(NSString *)localId error:(NSError **)error; -- (void)setObjectId:(NSString *)objectId forLocalId:(NSString *)localId; +- (BOOL)setObjectId:(NSString *)objectId forLocalId:(NSString *)localId error:(NSError **)error; - (NSString *)objectIdForLocalId:(NSString *)localId; // For testing only. diff --git a/Parse/Parse/Internal/Object/LocalIdStore/PFObjectLocalIdStore.m b/Parse/Parse/Internal/Object/LocalIdStore/PFObjectLocalIdStore.m index c11bdb152..099560384 100644 --- a/Parse/Parse/Internal/Object/LocalIdStore/PFObjectLocalIdStore.m +++ b/Parse/Parse/Internal/Object/LocalIdStore/PFObjectLocalIdStore.m @@ -134,8 +134,9 @@ + (BOOL)isLocalId:(NSString *)localId { /** * Grabs one entry in the local id map off the disk. */ -- (PFObjectLocalIdStoreMapEntry *)getMapEntry:(NSString *)localId { - PFConsistencyAssert([[self class] isLocalId:localId], @"Tried to get invalid local id: \"%@\".", localId); +- (PFObjectLocalIdStoreMapEntry *)getMapEntry:(NSString *)localId error:(NSError * __autoreleasing *) error { + + PFPreconditionBailAndSetError([[self class] isLocalId:localId], error, nil, @"Tried to get invalid local id: \"%@\".", localId); PFObjectLocalIdStoreMapEntry *entry = nil; @@ -153,7 +154,9 @@ - (PFObjectLocalIdStoreMapEntry *)getMapEntry:(NSString *)localId { if (objectId) { entry.objectId = objectId; if (entry.referenceCount > 0) { - [self putMapEntry:entry forLocalId:localId]; + if(![self putMapEntry:entry forLocalId:localId error:error]) { + return nil; + } } } } @@ -164,21 +167,23 @@ - (PFObjectLocalIdStoreMapEntry *)getMapEntry:(NSString *)localId { /** * Writes one entry to the local id map on disk. */ -- (void)putMapEntry:(PFObjectLocalIdStoreMapEntry *)entry forLocalId:(NSString *)localId { - PFConsistencyAssert([[self class] isLocalId:localId], @"Tried to get invalid local id: \"%@\".", localId); +- (BOOL)putMapEntry:(PFObjectLocalIdStoreMapEntry *)entry forLocalId:(NSString *)localId error:(NSError * __autoreleasing *)error { + PFPreconditionBailAndSetError([[self class] isLocalId:localId],error, NO, @"Tried to get invalid local id: \"%@\".", localId); NSString *file = [_diskPath stringByAppendingPathComponent:localId]; [entry writeToFile:file]; + return YES; } /** * Removes an entry from the local id map on disk. */ -- (void)removeMapEntry:(NSString *)localId { - PFConsistencyAssert([[self class] isLocalId:localId], @"Tried to get invalid local id: \"%@\".", localId); +- (BOOL)removeMapEntry:(NSString *)localId error:(NSError * __autoreleasing *)error { + PFPreconditionBailAndSetError([[self class] isLocalId:localId], error, NO, @"Tried to get invalid local id: \"%@\".", localId); NSString *file = [_diskPath stringByAppendingPathComponent:localId]; [[NSFileManager defaultManager] removeItemAtPath:file error:nil]; + return YES; } /** @@ -202,11 +207,14 @@ - (NSString *)createLocalId { /** * Increments the retain count of a local id on disk. */ -- (void)retainLocalIdOnDisk:(NSString *)localId { +- (BOOL)retainLocalIdOnDisk:(NSString *)localId error:(NSError **)error { @synchronized (_lock) { - PFObjectLocalIdStoreMapEntry *entry = [self getMapEntry:localId]; + PFObjectLocalIdStoreMapEntry *entry = [self getMapEntry:localId error:error]; + if (!entry) { + return NO; + } entry.referenceCount++; - [self putMapEntry:entry forLocalId:localId]; + return [self putMapEntry:entry forLocalId:localId error:error]; } } @@ -214,13 +222,16 @@ - (void)retainLocalIdOnDisk:(NSString *)localId { * Decrements the retain count of a local id on disk. * If the retain count hits zero, the id is forgotten forever. */ -- (void)releaseLocalIdOnDisk:(NSString *)localId { +- (BOOL)releaseLocalIdOnDisk:(NSString *)localId error:(NSError **)error { @synchronized (_lock) { - PFObjectLocalIdStoreMapEntry *entry = [self getMapEntry:localId]; + PFObjectLocalIdStoreMapEntry *entry = [self getMapEntry:localId error:error]; + if (!entry) { + return NO; + } if (--entry.referenceCount > 0) { - [self putMapEntry:entry forLocalId:localId]; + return [self putMapEntry:entry forLocalId:localId error:error]; } else { - [self removeMapEntry:localId]; + return [self removeMapEntry:localId error:error]; } } } @@ -228,14 +239,20 @@ - (void)releaseLocalIdOnDisk:(NSString *)localId { /** * Sets the objectId associated with a given local id. */ -- (void)setObjectId:(NSString *)objectId forLocalId:(NSString *)localId { +- (BOOL)setObjectId:(NSString *)objectId forLocalId:(NSString *)localId error:(NSError **)error { @synchronized (_lock) { - PFObjectLocalIdStoreMapEntry *entry = [self getMapEntry:localId]; + PFObjectLocalIdStoreMapEntry *entry = [self getMapEntry:localId error:error]; + if (!entry) { + return NO; + } if (entry.referenceCount > 0) { entry.objectId = objectId; - [self putMapEntry:entry forLocalId:localId]; + if (![self putMapEntry:entry forLocalId:localId error:error]) { + return NO; + } } _inMemoryCache[localId] = objectId; + return YES; } } @@ -250,7 +267,7 @@ - (NSString *)objectIdForLocalId:(NSString *)localId { return objectId; } - PFObjectLocalIdStoreMapEntry *entry = [self getMapEntry:localId]; + PFObjectLocalIdStoreMapEntry *entry = [self getMapEntry:localId error:nil]; return entry.objectId; } } diff --git a/Parse/Parse/Internal/Object/OperationSet/PFOperationSet.h b/Parse/Parse/Internal/Object/OperationSet/PFOperationSet.h index efacceb96..de82e194c 100644 --- a/Parse/Parse/Internal/Object/OperationSet/PFOperationSet.h +++ b/Parse/Parse/Internal/Object/OperationSet/PFOperationSet.h @@ -42,7 +42,8 @@ Converts this operation set into its REST format for serializing to the pinning store */ - (NSDictionary *)RESTDictionaryUsingObjectEncoder:(PFEncoder *)objectEncoder - operationSetUUIDs:(NSArray **)operationSetUUIDs; + operationSetUUIDs:(NSArray **)operationSetUUIDs + error:(NSError **)error; /** The inverse of RESTDictionaryUsingObjectEncoder. diff --git a/Parse/Parse/Internal/Object/OperationSet/PFOperationSet.m b/Parse/Parse/Internal/Object/OperationSet/PFOperationSet.m index 85b715c91..3a74b2aeb 100644 --- a/Parse/Parse/Internal/Object/OperationSet/PFOperationSet.m +++ b/Parse/Parse/Internal/Object/OperationSet/PFOperationSet.m @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "PFAssert.h" #import "PFOperationSet.h" #import "PFACL.h" @@ -71,15 +72,29 @@ - (void)mergeOperationSet:(PFOperationSet *)other { #pragma mark - Encoding ///-------------------------------------- -- (NSDictionary *)RESTDictionaryUsingObjectEncoder:(PFEncoder *)objectEncoder - operationSetUUIDs:(NSArray **)operationSetUUIDs { +- (nullable NSDictionary *)RESTDictionaryUsingObjectEncoder:(PFEncoder *)objectEncoder + operationSetUUIDs:(NSArray **)operationSetUUIDs + error:(NSError * __autoreleasing *)error { NSMutableDictionary *operationSetResult = [[NSMutableDictionary alloc] init]; + __block NSError *encodingError; + __block BOOL wasStopped = false; [self.dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - operationSetResult[key] = [obj encodeWithObjectEncoder:objectEncoder]; + id result = [obj encodeWithObjectEncoder:objectEncoder error:&encodingError]; + if (!result && encodingError) { + *stop = YES; + wasStopped = YES; + return; + } + operationSetResult[key] = result; }]; + if (wasStopped && error) { + *error = encodingError; + return nil; + } + operationSetResult[PFOperationSetKeyUUID] = self.uuid; - operationSetResult[PFOperationSetKeyUpdatedAt] = [objectEncoder encodeObject:self.updatedAt]; + operationSetResult[PFOperationSetKeyUpdatedAt] = [objectEncoder encodeObject:self.updatedAt error:nil]; if (self.saveEventually) { operationSetResult[PFOperationSetKeyIsSaveEventually] = @YES; diff --git a/Parse/Parse/Internal/Object/PFObjectPrivate.h b/Parse/Parse/Internal/Object/PFObjectPrivate.h index c6423af64..0b899fb42 100644 --- a/Parse/Parse/Internal/Object/PFObjectPrivate.h +++ b/Parse/Parse/Internal/Object/PFObjectPrivate.h @@ -207,7 +207,7 @@ ///-------------------------------------- #pragma mark - Validations ///-------------------------------------- -- (void)_checkSaveParametersWithCurrentUser:(PFUser *)currentUser; +- (BOOL)_checkSaveParametersWithCurrentUser:(PFUser *)currentUser error:(NSError **)error; /** Checks if Parse class name could be used to initialize a given instance of PFObject or it's subclass. */ @@ -217,7 +217,7 @@ #pragma mark - Serialization helpers ///-------------------------------------- - (NSString *)getOrCreateLocalId; -- (void)resolveLocalId; +- (BOOL)resolveLocalId:(NSError **) error; + (id)_objectFromDictionary:(NSDictionary *)dictionary defaultClassName:(NSString *)defaultClassName @@ -237,21 +237,25 @@ usingMigrationBlock:(BFContinuationBlock)block; - (NSMutableDictionary *)_convertToDictionaryForSaving:(PFOperationSet *)changes - withObjectEncoder:(PFEncoder *)encoder; + withObjectEncoder:(PFEncoder *)encoder + error:(NSError **)error; ///-------------------------------------- #pragma mark - REST operations ///-------------------------------------- - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder - operationSetUUIDs:(NSArray **)operationSetUUIDs; + operationSetUUIDs:(NSArray **)operationSetUUIDs + error:(NSError **)error; - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder operationSetUUIDs:(NSArray **)operationSetUUIDs state:(PFObjectState *)state operationSetQueue:(NSArray *)queue - deletingEventuallyCount:(NSUInteger)deletingEventuallyCount; + deletingEventuallyCount:(NSUInteger)deletingEventuallyCount + error:(NSError **)error; -- (void)mergeFromRESTDictionary:(NSDictionary *)object - withDecoder:(PFDecoder *)decoder; +- (BOOL)mergeFromRESTDictionary:(NSDictionary *)object + withDecoder:(PFDecoder *)decoder + error:(NSError **)error; ///-------------------------------------- #pragma mark - Data helpers @@ -283,7 +287,8 @@ ///-------------------------------------- - (PFRESTCommand *)_constructSaveCommandForChanges:(PFOperationSet *)changes sessionToken:(NSString *)sessionToken - objectEncoder:(PFEncoder *)encoder; + objectEncoder:(PFEncoder *)encoder + error:(NSError **)error; - (PFRESTCommand *)_currentDeleteCommandWithSessionToken:(NSString *)sessionToken; ///-------------------------------------- diff --git a/Parse/Parse/Internal/Object/State/PFObjectState.h b/Parse/Parse/Internal/Object/State/PFObjectState.h index 961a35eb2..e5522a66a 100644 --- a/Parse/Parse/Internal/Object/State/PFObjectState.h +++ b/Parse/Parse/Internal/Object/State/PFObjectState.h @@ -59,7 +59,7 @@ typedef void(^PFObjectStateMutationBlock)(PFMutableObjectState *state); @return `NSDictionary` instance representing object state. */ -- (NSDictionary *)dictionaryRepresentationWithObjectEncoder:(PFEncoder *)objectEncoder NS_REQUIRES_SUPER; +- (NSDictionary *)dictionaryRepresentationWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error NS_REQUIRES_SUPER; ///-------------------------------------- #pragma mark - Mutating diff --git a/Parse/Parse/Internal/Object/State/PFObjectState.m b/Parse/Parse/Internal/Object/State/PFObjectState.m index ba3eafa03..765a13700 100644 --- a/Parse/Parse/Internal/Object/State/PFObjectState.m +++ b/Parse/Parse/Internal/Object/State/PFObjectState.m @@ -106,7 +106,7 @@ - (void)setServerData:(NSDictionary *)serverData { #pragma mark - Coding ///-------------------------------------- -- (NSDictionary *)dictionaryRepresentationWithObjectEncoder:(PFEncoder *)objectEncoder { +- (NSDictionary *)dictionaryRepresentationWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError * __autoreleasing *)error { NSMutableDictionary *result = [NSMutableDictionary dictionary]; if (self.objectId) { result[PFObjectObjectIdRESTKey] = self.objectId; @@ -117,9 +117,21 @@ - (NSDictionary *)dictionaryRepresentationWithObjectEncoder:(PFEncoder *)objectE if (self.updatedAt) { result[PFObjectUpdatedAtRESTKey] = [[PFDateFormatter sharedFormatter] preciseStringFromDate:self.updatedAt]; } + __block NSError *encodingError; + __block BOOL failed = NO; [self.serverData enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - result[key] = [objectEncoder encodeObject:obj]; + id encoded = [objectEncoder encodeObject:obj error:&encodingError]; + if (!encoded && encodingError) { + *stop = YES; + failed = YES; + return; + } + result[key] = encoded; }]; + if (failed && encodingError) { + *error = encodingError; + return nil; + } return [result copy]; } diff --git a/Parse/Parse/Internal/PFAssert.h b/Parse/Parse/Internal/PFAssert.h index 2b0cd7401..2725af61a 100644 --- a/Parse/Parse/Internal/PFAssert.h +++ b/Parse/Parse/Internal/PFAssert.h @@ -8,6 +8,7 @@ */ #import "PFMacros.h" +#import "PFErrorUtilities.h" #ifndef Parse_PFAssert_h #define Parse_PFAssert_h @@ -57,6 +58,53 @@ do { \ } \ } while(0) +/* + Returns an error with the description, if a condition isn't met + */ +#define PFPrecondition(condition, description, ...) \ +if (!(condition)) { \ + return [PFErrorUtilities errorWithCode:-1 message:[NSString stringWithFormat:description, ##__VA_ARGS__]];\ +} + +/** + Returns a consistency error. + */ +#define PFPreconditionFailure(description, ...) \ +PFPrecondition(NO, description, ##__VA_ARGS__) + +/** +Sets a recoverable error for propagation + */ +#define PFPreconditionBailAndSetError(condition, error, rval, description, ...) \ +if (!(condition) && error && *error == nil) { \ + *error = [PFErrorUtilities errorWithCode:-1 message:[NSString stringWithFormat:description, ##__VA_ARGS__]];\ + return rval;\ +} + +/* + Returns the passed value if the condition isn't met and the *error is set + */ +#define PFPreconditionBailOnError(condition, error, rval) \ +if (!(condition) && error && *error) { \ + return rval;\ +} + +/* + Returns an failed task with the passed error if a contition isn't met and the error is set + */ +#define PFPreconditionReturnFailedTask(condition, error) \ +if (!(condition) && error) { \ + return [BFTask taskWithError:error];\ +} + +/* + Returns a failed BFTask with an error description if the condition isn't met + */ +#define PFPreconditionWithTask(condition, description, ...) \ +if (!(condition)) { \ + return [BFTask taskWithError:[PFErrorUtilities errorWithCode:-1 message:[NSString stringWithFormat:description, ##__VA_ARGS__]]];\ +} + /** Raises an `NSInternalInconsistencyException`. Use `description` to supply the way to fix the exception. */ diff --git a/Parse/Parse/Internal/PFCommandCache.m b/Parse/Parse/Internal/PFCommandCache.m index 13c9873fd..6bd48c1db 100644 --- a/Parse/Parse/Internal/PFCommandCache.m +++ b/Parse/Parse/Internal/PFCommandCache.m @@ -205,7 +205,11 @@ - (BFTask *)_didFinishRunningCommand:(id)command if (dictionaryResult != nil) { NSString *objectId = dictionaryResult[@"objectId"]; if (objectId) { - [self.coreDataSource.objectLocalIdStore setObjectId:objectId forLocalId:command.localId]; + NSError *error; + [self.coreDataSource.objectLocalIdStore setObjectId:objectId forLocalId:command.localId error:&error]; + if (error != nil) { + return [BFTask taskWithError:error]; + } } } } @@ -290,7 +294,9 @@ - (BFTask *)_saveCommandToCacheInBackground:(id)command @strongify(self); NSError *error = nil; - NSData *data = [NSJSONSerialization dataWithJSONObject:[command dictionaryRepresentation] + NSDictionary *JSON = [command dictionaryRepresentation:&error]; + PFPreconditionReturnFailedTask(JSON, error); + NSData *data = [NSJSONSerialization dataWithJSONObject:JSON options:0 error:&error]; NSUInteger commandSize = data.length; diff --git a/Parse/Parse/Internal/PFEventuallyPin.m b/Parse/Parse/Internal/PFEventuallyPin.m index ee8434f84..76049f12f 100644 --- a/Parse/Parse/Internal/PFEventuallyPin.m +++ b/Parse/Parse/Internal/PFEventuallyPin.m @@ -92,7 +92,11 @@ + (BFTask *)pinEventually:(PFObject *)object forCommand:(id)co + (BFTask *)pinEventually:(PFObject *)object forCommand:(id)command withUUID:(NSString *)uuid { PFEventuallyPinType type = [self _pinTypeForCommand:command]; - NSDictionary *commandDictionary = (type == PFEventuallyPinTypeCommand ? [command dictionaryRepresentation] : nil); + NSError *error; + NSDictionary *commandDictionary = (type == PFEventuallyPinTypeCommand ? [command dictionaryRepresentation:&error] : nil); + if (type == PFEventuallyPinTypeCommand) { + PFPreconditionReturnFailedTask(commandDictionary, error); + } return [self _pinEventually:object type:type uuid:uuid diff --git a/Parse/Parse/Internal/PFGeoPointPrivate.h b/Parse/Parse/Internal/PFGeoPointPrivate.h index 0345c1a00..00c24ec25 100644 --- a/Parse/Parse/Internal/PFGeoPointPrivate.h +++ b/Parse/Parse/Internal/PFGeoPointPrivate.h @@ -23,7 +23,7 @@ extern const double EARTH_RADIUS_KILOMETERS; /* Gets the encoded format for an GeoPoint. */ -- (NSDictionary *)encodeIntoDictionary; +- (NSDictionary *)encodeIntoDictionary:(NSError **)error; /** Creates an GeoPoint from its encoded format. diff --git a/Parse/Parse/Internal/PFNetworkCommand.h b/Parse/Parse/Internal/PFNetworkCommand.h index ba5ab85a1..6846d8604 100644 --- a/Parse/Parse/Internal/PFNetworkCommand.h +++ b/Parse/Parse/Internal/PFNetworkCommand.h @@ -27,7 +27,7 @@ ///-------------------------------------- + (instancetype)commandFromDictionaryRepresentation:(NSDictionary *)dictionary; -- (NSDictionary *)dictionaryRepresentation; +- (NSDictionary *)dictionaryRepresentation:(NSError **)error; + (BOOL)isValidDictionaryRepresentation:(NSDictionary *)dictionary; @@ -42,6 +42,6 @@ has not yet been saved, and thus has no objectId, then this method raises an exception. */ -- (void)resolveLocalIds; +- (BOOL)resolveLocalIds:(NSError **)error; @end diff --git a/Parse/Parse/Internal/PFPinningEventuallyQueue.m b/Parse/Parse/Internal/PFPinningEventuallyQueue.m index 59ea4c1b5..1853c0a6c 100644 --- a/Parse/Parse/Internal/PFPinningEventuallyQueue.m +++ b/Parse/Parse/Internal/PFPinningEventuallyQueue.m @@ -169,7 +169,8 @@ - (NSArray *)_pendingCommandIdentifiers { PFOperationSet *operationSet = _operationSetUUIDToOperationSet[eventuallyPin.operationSetUUID]; return [eventuallyPin.object _constructSaveCommandForChanges:operationSet sessionToken:eventuallyPin.sessionToken - objectEncoder:[PFPointerObjectEncoder objectEncoder]]; + objectEncoder:[PFPointerObjectEncoder objectEncoder] + error:error]; } case PFEventuallyPinTypeDelete: return [eventuallyPin.object _currentDeleteCommandWithSessionToken:eventuallyPin.sessionToken]; diff --git a/Parse/Parse/Internal/PFPolygonPrivate.h b/Parse/Parse/Internal/PFPolygonPrivate.h index 0d5ed8ec8..02e769c6c 100644 --- a/Parse/Parse/Internal/PFPolygonPrivate.h +++ b/Parse/Parse/Internal/PFPolygonPrivate.h @@ -20,7 +20,7 @@ /* Gets the encoded format for a Polygon. */ -- (NSDictionary *)encodeIntoDictionary; +- (NSDictionary *)encodeIntoDictionary:(NSError **)error; /** Creates a Polygon from its encoded format. diff --git a/Parse/Parse/Internal/Purchase/Controller/PFPurchaseController.m b/Parse/Parse/Internal/Purchase/Controller/PFPurchaseController.m index b9b6d90e1..494c4b32a 100644 --- a/Parse/Parse/Internal/Purchase/Controller/PFPurchaseController.m +++ b/Parse/Parse/Internal/Purchase/Controller/PFPurchaseController.m @@ -157,11 +157,12 @@ - (BFTask *)downloadAssetAsyncForTransaction:(SKPaymentTransaction *)transaction } NSDictionary *params = [[PFEncoder objectEncoder] encodeObject:@{ @"receipt" : appStoreReceipt, - @"productIdentifier" : productIdentifier }]; + @"productIdentifier" : productIdentifier } error:nil]; PFRESTCommand *command = [PFRESTCommand commandWithHTTPPath:@"validate_purchase" httpMethod:PFHTTPRequestMethodPOST parameters:params - sessionToken:sessionToken]; + sessionToken:sessionToken + error:nil]; BFTask *task = [self.dataSource.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; @weakify(self); return [task continueWithSuccessBlock:^id(BFTask *task) { diff --git a/Parse/Parse/Internal/Push/Controller/PFPushController.m b/Parse/Parse/Internal/Push/Controller/PFPushController.m index 91a4096fc..f66ef3b4c 100644 --- a/Parse/Parse/Internal/Push/Controller/PFPushController.m +++ b/Parse/Parse/Internal/Push/Controller/PFPushController.m @@ -43,7 +43,9 @@ - (BFTask *)sendPushNotificationAsyncWithState:(PFPushState *)state @weakify(self); return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ @strongify(self); - PFRESTCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:sessionToken]; + NSError *error; + PFRESTCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:sessionToken error:&error]; + PFPreconditionReturnFailedTask(command, error); return [self.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithSuccessBlock:^id(BFTask *task) { return @(task.result != nil); diff --git a/Parse/Parse/Internal/Query/Controller/PFCachedQueryController.m b/Parse/Parse/Internal/Query/Controller/PFCachedQueryController.m index 55631f2f6..e52585b2f 100644 --- a/Parse/Parse/Internal/Query/Controller/PFCachedQueryController.m +++ b/Parse/Parse/Internal/Query/Controller/PFCachedQueryController.m @@ -153,7 +153,8 @@ - (BFTask *)_runNetworkCommandAsync:(PFRESTCommand *)command ///-------------------------------------- - (NSString *)cacheKeyForQueryState:(PFQueryState *)queryState sessionToken:(NSString *)sessionToken { - return [PFRESTQueryCommand findCommandForQueryState:queryState withSessionToken:sessionToken].cacheKey; + // TODO (flovilmart): verify if safe to swallow error here + return [PFRESTQueryCommand findCommandForQueryState:queryState withSessionToken:sessionToken error:nil].cacheKey; } - (BOOL)hasCachedResultForQueryState:(PFQueryState *)queryState sessionToken:(NSString *)sessionToken { diff --git a/Parse/Parse/Internal/Query/Controller/PFQueryController.m b/Parse/Parse/Internal/Query/Controller/PFQueryController.m index 9058ab305..24dd5f374 100644 --- a/Parse/Parse/Internal/Query/Controller/PFQueryController.m +++ b/Parse/Parse/Internal/Query/Controller/PFQueryController.m @@ -62,7 +62,9 @@ - (BFTask *)findObjectsAsyncForQueryState:(PFQueryState *)queryState return [BFTask cancelledTask]; } - PFRESTCommand *command = [PFRESTQueryCommand findCommandForQueryState:queryState withSessionToken:sessionToken]; + NSError *error; + PFRESTCommand *command = [PFRESTQueryCommand findCommandForQueryState:queryState withSessionToken:sessionToken error:&error]; + PFPreconditionReturnFailedTask(command, error); querySent = (queryState.trace ? [NSDate date] : nil); return [self runNetworkCommandAsync:command withCancellationToken:cancellationToken @@ -111,9 +113,13 @@ - (BFTask *)countObjectsAsyncForQueryState:(PFQueryState *)queryState return [BFTask cancelledTask]; } + NSError *error; PFRESTQueryCommand *findCommand = [PFRESTQueryCommand findCommandForQueryState:queryState - withSessionToken:sessionToken]; - PFRESTCommand *countCommand = [PFRESTQueryCommand countCommandFromFindCommand:findCommand]; + withSessionToken:sessionToken + error:&error]; + PFPreconditionReturnFailedTask(findCommand, error); + PFRESTCommand *countCommand = [PFRESTQueryCommand countCommandFromFindCommand:findCommand error:&error]; + PFPreconditionReturnFailedTask(countCommand, error); return [self runNetworkCommandAsync:countCommand withCancellationToken:cancellationToken forQueryState:queryState]; diff --git a/Parse/Parse/Internal/Relation/PFRelationPrivate.h b/Parse/Parse/Internal/Relation/PFRelationPrivate.h index 26d26fdd4..3b3763eae 100644 --- a/Parse/Parse/Internal/Relation/PFRelationPrivate.h +++ b/Parse/Parse/Internal/Relation/PFRelationPrivate.h @@ -19,7 +19,7 @@ + (PFRelation *)relationWithTargetClass:(NSString *)targetClass; + (PFRelation *)relationFromDictionary:(NSDictionary *)dictionary withDecoder:(PFDecoder *)decoder; - (void)ensureParentIs:(PFObject *)someParent andKeyIs:(NSString *)someKey; -- (NSDictionary *)encodeIntoDictionary; +- (NSDictionary *)encodeIntoDictionary:(NSError **)error; - (BOOL)_hasKnownObject:(PFObject *)object; - (void)_addKnownObject:(PFObject *)object; - (void)_removeKnownObject:(PFObject *)object; diff --git a/Parse/Parse/Internal/User/AuthenticationProviders/Controller/PFUserAuthenticationController.m b/Parse/Parse/Internal/User/AuthenticationProviders/Controller/PFUserAuthenticationController.m index 80887b66f..e4a70a7a5 100644 --- a/Parse/Parse/Internal/User/AuthenticationProviders/Controller/PFUserAuthenticationController.m +++ b/Parse/Parse/Internal/User/AuthenticationProviders/Controller/PFUserAuthenticationController.m @@ -51,12 +51,12 @@ + (instancetype)controllerWithDataSource:(id)delegate forAuthType:(NSString *)authType { +- (void)registerAuthenticationDelegate:(id)delegate + forAuthType:(NSString *)authType { PFParameterAssert(delegate, @"Authentication delegate can't be `nil`."); PFParameterAssert(authType, @"`authType` can't be `nil`."); - PFConsistencyAssert(![self authenticationDelegateForAuthType:authType], + PFParameterAssert(![self authenticationDelegateForAuthType:authType], @"Authentication delegate already registered for authType `%@`.", authType); - dispatch_sync(_dataAccessQueue, ^{ _authenticationDelegates[authType] = delegate; }); diff --git a/Parse/Parse/Internal/User/Controller/PFUserController.m b/Parse/Parse/Internal/User/Controller/PFUserController.m index 43bfd7db3..6b75673be 100644 --- a/Parse/Parse/Internal/User/Controller/PFUserController.m +++ b/Parse/Parse/Internal/User/Controller/PFUserController.m @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ - +#import "PFAssert.h" #import "PFUserController.h" #import "BFTask+Private.h" @@ -50,7 +50,9 @@ - (BFTask *)logInCurrentUserAsyncWithSessionToken:(NSString *)sessionToken { @weakify(self); return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ @strongify(self); - PFRESTCommand *command = [PFRESTUserCommand getCurrentUserCommandWithSessionToken:sessionToken]; + NSError *error = nil; + PFRESTCommand *command = [PFRESTUserCommand getCurrentUserCommandWithSessionToken:sessionToken error:&error]; + PFPreconditionReturnFailedTask(command, error); return [self.commonDataSource.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithSuccessBlock:^id(BFTask *task) { @@ -80,9 +82,12 @@ - (BFTask *)logInCurrentUserAsyncWithUsername:(NSString *)username revocableSession:(BOOL)revocableSession { @weakify(self); return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ + NSError *error = nil; PFRESTCommand *command = [PFRESTUserCommand logInUserCommandWithUsername:username password:password - revocableSession:revocableSession]; + revocableSession:revocableSession + error:&error]; + PFPreconditionReturnFailedTask(command, error); return [self.commonDataSource.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithSuccessBlock:^id(BFTask *task) { @@ -114,9 +119,12 @@ - (BFTask *)logInCurrentUserAsyncWithAuthType:(NSString *)authType @weakify(self); return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ @strongify(self); + NSError *error; PFRESTCommand *command = [PFRESTUserCommand serviceLoginUserCommandWithAuthenticationType:authType authenticationData:authData - revocableSession:revocableSession]; + revocableSession:revocableSession + error:&error]; + PFPreconditionReturnFailedTask(command, error); return [self.commonDataSource.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithSuccessBlock:^id(BFTask *task) { @@ -141,7 +149,9 @@ - (BFTask *)requestPasswordResetAsyncForEmail:(NSString *)email { @weakify(self); return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ @strongify(self); - PFRESTCommand *command = [PFRESTUserCommand resetPasswordCommandForUserWithEmail:email]; + NSError *error = nil; + PFRESTCommand *command = [PFRESTUserCommand resetPasswordCommandForUserWithEmail:email error:&error]; + PFPreconditionReturnFailedTask(command, error); return [self.commonDataSource.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithSuccessResult:nil]; @@ -155,7 +165,9 @@ - (BFTask *)logOutUserAsyncWithSessionToken:(NSString *)sessionToken { @weakify(self); return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{ @strongify(self); - PFRESTCommand *command = [PFRESTUserCommand logOutUserCommandWithSessionToken:sessionToken]; + NSError *error = nil; + PFRESTCommand *command = [PFRESTUserCommand logOutUserCommandWithSessionToken:sessionToken error:&error]; + PFPreconditionReturnFailedTask(command, error); return [self.commonDataSource.commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithSuccessResult:nil]; diff --git a/Parse/Parse/Internal/User/State/PFUserState.m b/Parse/Parse/Internal/User/State/PFUserState.m index a57130b1f..fb0801594 100644 --- a/Parse/Parse/Internal/User/State/PFUserState.m +++ b/Parse/Parse/Internal/User/State/PFUserState.m @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#import "PFAssert.h" #import "PFUserState.h" #import "PFUserState_Private.h" @@ -48,8 +49,10 @@ + (instancetype)stateWithState:(PFUserState *)state { #pragma mark - Serialization ///-------------------------------------- -- (NSDictionary *)dictionaryRepresentationWithObjectEncoder:(PFEncoder *)objectEncoder { - NSMutableDictionary *dictionary = [[super dictionaryRepresentationWithObjectEncoder:objectEncoder] mutableCopy]; +- (nullable NSDictionary *)dictionaryRepresentationWithObjectEncoder:(PFEncoder *)objectEncoder error:(NSError **)error { + NSDictionary *representation = [super dictionaryRepresentationWithObjectEncoder:objectEncoder error:error]; + PFPreconditionBailOnError(representation, error, nil); + NSMutableDictionary *dictionary = [representation mutableCopy]; [dictionary removeObjectForKey:PFUserPasswordRESTKey]; return dictionary; } diff --git a/Parse/Parse/PFACL.h b/Parse/Parse/PFACL.h index 074bea246..59e2af022 100644 --- a/Parse/Parse/PFACL.h +++ b/Parse/Parse/PFACL.h @@ -47,12 +47,12 @@ NS_ASSUME_NONNULL_BEGIN /** Controls whether the public is allowed to read this object. */ -@property (nonatomic, assign, getter=getPublicReadAccess) BOOL publicReadAccess; +@property (nonatomic, assign, getter=getPublicReadAccess) BOOL publicReadAccess NS_SWIFT_NAME(hasPublicReadAccess); /** Controls whether the public is allowed to write this object. */ -@property (nonatomic, assign, getter=getPublicWriteAccess) BOOL publicWriteAccess; +@property (nonatomic, assign, getter=getPublicWriteAccess) BOOL publicWriteAccess NS_SWIFT_NAME(hasPublicWriteAccess); ///-------------------------------------- #pragma mark - Controlling Access Per-User diff --git a/Parse/Parse/PFACL.m b/Parse/Parse/PFACL.m index f6987fe3a..935913da6 100644 --- a/Parse/Parse/PFACL.m +++ b/Parse/Parse/PFACL.m @@ -319,7 +319,7 @@ - (BOOL)getWriteAccessForUser:(PFUser *)user { return [self getWriteAccessForUserId:objectId]; } -- (NSDictionary *)encodeIntoDictionary { +- (NSDictionary *)encodeIntoDictionary:(NSError **)error { return self.state.permissions; } @@ -362,7 +362,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder { } - (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:[self encodeIntoDictionary] forKey:PFACLCodingDataKey_]; + [coder encodeObject:[self encodeIntoDictionary:nil] forKey:PFACLCodingDataKey_]; } @end diff --git a/Parse/Parse/PFConstants.h b/Parse/Parse/PFConstants.h index 2ef96c06d..b25566be1 100644 --- a/Parse/Parse/PFConstants.h +++ b/Parse/Parse/PFConstants.h @@ -13,7 +13,7 @@ #pragma mark - SDK Version ///-------------------------------------- -#define PARSE_VERSION @"1.16.0" +#define PARSE_VERSION @"1.17.0-alpha.5" ///-------------------------------------- #pragma mark - Platform diff --git a/Parse/Parse/PFEncoder.h b/Parse/Parse/PFEncoder.h index 2d77fd308..4b18091d4 100644 --- a/Parse/Parse/PFEncoder.h +++ b/Parse/Parse/PFEncoder.h @@ -24,8 +24,8 @@ + (instancetype)objectEncoder; -- (id)encodeObject:(id)object; -- (id)encodeParseObject:(PFObject *)object; +- (id)encodeObject:(id)object error:(NSError **)error; +- (id)encodeParseObject:(PFObject *)object error:(NSError **)error; @end diff --git a/Parse/Parse/PFEncoder.m b/Parse/Parse/PFEncoder.m index 6650217ff..7af285952 100644 --- a/Parse/Parse/PFEncoder.m +++ b/Parse/Parse/PFEncoder.m @@ -33,9 +33,9 @@ + (instancetype)objectEncoder { return encoder; } -- (id)encodeObject:(id)object { +- (id)encodeObject:(id)object error:(NSError * __autoreleasing *) error { if ([object isKindOfClass:[PFObject class]]) { - return [self encodeParseObject:object]; + return [self encodeParseObject:object error:error]; } else if ([object isKindOfClass:[NSData class]]) { return @{ @"__type" : @"Bytes", @@ -67,42 +67,56 @@ - (id)encodeObject:(id)object { } else if ([object isKindOfClass:[PFFieldOperation class]]) { // Always encode PFFieldOperation with PFPointerOrLocalId - return [object encodeWithObjectEncoder:[PFPointerOrLocalIdObjectEncoder objectEncoder]]; + return [object encodeWithObjectEncoder:[PFPointerOrLocalIdObjectEncoder objectEncoder] error:error]; } else if ([object isKindOfClass:[PFACL class]]) { // TODO (hallucinogen): pass object encoder here - return [object encodeIntoDictionary]; + return [object encodeIntoDictionary:error]; } else if ([object isKindOfClass:[PFGeoPoint class]]) { // TODO (hallucinogen): pass object encoder here - return [object encodeIntoDictionary]; + return [object encodeIntoDictionary:error]; } else if ([object isKindOfClass:[PFPolygon class]]) { // TODO (hallucinogen): pass object encoder here - return [object encodeIntoDictionary]; + return [object encodeIntoDictionary:error]; } else if ([object isKindOfClass:[PFRelation class]]) { // TODO (hallucinogen): pass object encoder here - return [object encodeIntoDictionary]; + return [object encodeIntoDictionary:error]; } else if ([object isKindOfClass:[NSArray class]]) { NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:[object count]]; for (id elem in object) { - [newArray addObject:[self encodeObject:elem]]; + id encodedElem = [self encodeObject:elem error:error]; + PFPreconditionBailOnError(encodedElem, error, nil); + [newArray addObject:encodedElem]; } return newArray; } else if ([object isKindOfClass:[NSDictionary class]]) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:[object count]]; + __block BOOL encodingFailed = NO; + __block NSError *encodingError = nil; [object enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - dict[key] = [self encodeObject:obj]; + id result = [self encodeObject:obj error:&encodingError]; + if (!result && encodingError) { + encodingFailed = YES; + *stop = YES; + } else { + dict[key] = result; + } }]; + if (encodingFailed) { + *error = encodingError; + return nil; + } return dict; } return object; } -- (id)encodeParseObject:(PFObject *)object { +- (id)encodeParseObject:(PFObject *)object error:(NSError **)error { // Do nothing here return nil; } @@ -124,7 +138,7 @@ + (instancetype)objectEncoder { return encoder; } -- (id)encodeParseObject:(PFObject *)object { +- (id)encodeParseObject:(PFObject *)object error:(NSError **)error { PFConsistencyAssertionFailure(@"PFObjects are not allowed here."); return nil; } @@ -146,7 +160,7 @@ + (instancetype)objectEncoder { return instance; } -- (id)encodeParseObject:(PFObject *)object { +- (id)encodeParseObject:(PFObject *)object error:(NSError **)error { if (object.objectId) { return @{ @"__type" : @"Pointer", @@ -178,9 +192,9 @@ + (instancetype)objectEncoder { return encoder; } -- (id)encodeParseObject:(PFObject *)object { - PFConsistencyAssert(object.objectId, @"Tried to save an object with a new, unsaved child."); - return [super encodeParseObject:object]; +- (id)encodeParseObject:(PFObject *)object error:(NSError * __autoreleasing *)error { + PFPreconditionBailAndSetError(object.objectId, error, nil, @"Tried to save an object with a new, unsaved child."); + return [super encodeParseObject:object error:error]; } @end @@ -222,7 +236,7 @@ + (instancetype)objectEncoderWithOfflineStore:(PFOfflineStore *)store database:( return [[self alloc] initWithOfflineStore:store database:database]; } -- (id)encodeParseObject:(PFObject *)object { +- (id)encodeParseObject:(PFObject *)object error:(NSError **)error { if (object.objectId) { return @{ @"__type" : @"Pointer", @"objectId" : object.objectId, diff --git a/Parse/Parse/PFGeoPoint.m b/Parse/Parse/PFGeoPoint.m index 580aad297..2b0c5a931 100644 --- a/Parse/Parse/PFGeoPoint.m +++ b/Parse/Parse/PFGeoPoint.m @@ -103,7 +103,7 @@ - (double)distanceInKilometersTo:(PFGeoPoint *)point { static NSString *const PFGeoPointCodingLatitudeKey = @"latitude"; static NSString *const PFGeoPointCodingLongitudeKey = @"longitude"; -- (NSDictionary *)encodeIntoDictionary { +- (NSDictionary *)encodeIntoDictionary:(NSError **)error { return @{ PFGeoPointCodingTypeKey : @"GeoPoint", PFGeoPointCodingLatitudeKey : @(self.latitude), @@ -184,7 +184,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder { } - (void)encodeWithCoder:(NSCoder *)coder { - NSDictionary *dictionary = [self encodeIntoDictionary]; + NSDictionary *dictionary = [self encodeIntoDictionary:nil]; [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [coder encodeObject:obj forKey:key]; }]; diff --git a/Parse/Parse/PFObject.m b/Parse/Parse/PFObject.m index bbebe42e4..e518d1bd5 100644 --- a/Parse/Parse/PFObject.m +++ b/Parse/Parse/PFObject.m @@ -204,41 +204,64 @@ + (BFTask *)_enqueue:(BFTask *(^)(BFTask *toAwait))taskStart forObjects:(NSArray @param seenNew The set of new objects that have already been seen since the last existing object. */ -+ (void)collectDirtyChildren:(id)node ++ (BOOL)collectDirtyChildren:(id)node children:(NSMutableSet *)dirtyChildren files:(NSMutableSet *)dirtyFiles seen:(NSSet *)seen seenNew:(NSSet *)seenNew - currentUser:(PFUser *)currentUser { + currentUser:(PFUser *)currentUser + error:(NSError * __autoreleasing *)error { if ([node isKindOfClass:[NSArray class]]) { for (id elem in node) { + NSError *localError; + BOOL succeeded; @autoreleasepool { - [self collectDirtyChildren:elem - children:dirtyChildren - files:dirtyFiles - seen:seen - seenNew:seenNew - currentUser:currentUser]; + succeeded = [self collectDirtyChildren:elem + children:dirtyChildren + files:dirtyFiles + seen:seen + seenNew:seenNew + currentUser:currentUser + error:&localError]; + } + if (!succeeded) { + *error = localError; + return NO; } } } else if ([node isKindOfClass:[NSDictionary class]]) { + __block BOOL wasStopped = NO; + __block NSError *localError; [node enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - [self collectDirtyChildren:obj - children:dirtyChildren - files:dirtyFiles - seen:seen - seenNew:seenNew - currentUser:currentUser]; + if (![self collectDirtyChildren:obj + children:dirtyChildren + files:dirtyFiles + seen:seen + seenNew:seenNew + currentUser:currentUser + error:&localError]) { + *stop = YES; + wasStopped = YES; + } }]; + if (wasStopped) { + *error = localError; + return NO; + } } else if ([node isKindOfClass:[PFACL class]]) { PFACL *acl = (PFACL *)node; if ([acl hasUnresolvedUser]) { - [self collectDirtyChildren:currentUser - children:dirtyChildren - files:dirtyFiles - seen:seen - seenNew:seenNew - currentUser:currentUser]; + NSError *localError; + if (![self collectDirtyChildren:currentUser + children:dirtyChildren + files:dirtyFiles + seen:seen + seenNew:seenNew + currentUser:currentUser + error:&localError]) { + *error = localError; + return NO; + } } } else if ([node isKindOfClass:[PFObject class]]) { @@ -251,8 +274,10 @@ + (void)collectDirtyChildren:(id)node if (object.objectId) { seenNew = [NSSet set]; } else { - if ([seenNew containsObject:object]) { - PFConsistencyAssertionFailure(@"Found a circular dependency when saving."); + if ([seenNew containsObject:object] && error) { + *error = [PFErrorUtilities errorWithCode:kPFErrorInvalidPointer + message:@"Found a circular dependency when saving."]; + return NO; } seenNew = [seenNew setByAddingObject:object]; } @@ -261,7 +286,7 @@ + (void)collectDirtyChildren:(id)node // problem, but we shouldn't recurse any deeper, because it would be // an infinite recursion. if ([seen containsObject:object]) { - return; + return YES; } seen = [seen setByAddingObject:object]; @@ -271,12 +296,17 @@ + (void)collectDirtyChildren:(id)node toSearch = [object._estimatedData.dictionaryRepresentation copy]; } - [self collectDirtyChildren:toSearch + NSError *localError; + if (![self collectDirtyChildren:toSearch children:dirtyChildren files:dirtyFiles seen:seen seenNew:seenNew - currentUser:currentUser]; + currentUser:currentUser + error:&localError]) { + *error = localError; + return NO; + } if ([object isDirty:NO]) { [dirtyChildren addObject:object]; @@ -287,20 +317,23 @@ + (void)collectDirtyChildren:(id)node [dirtyFiles addObject:node]; } } + return YES; } // Helper version of collectDirtyChildren:children:seen:seenNew so that callers // don't have to add the internally used parameters. -+ (void)collectDirtyChildren:(id)child ++ (BOOL)collectDirtyChildren:(id)child children:(NSMutableSet *)dirtyChildren files:(NSMutableSet *)dirtyFiles - currentUser:(PFUser *)currentUser { - [self collectDirtyChildren:child - children:dirtyChildren - files:dirtyFiles - seen:[NSSet set] - seenNew:[NSSet set] - currentUser:currentUser]; + currentUser:(PFUser *)currentUser + error:(NSError **)error { + return [self collectDirtyChildren:child + children:dirtyChildren + files:dirtyFiles + seen:[NSSet set] + seenNew:[NSSet set] + currentUser:currentUser + error:error]; } // Returns YES if the given object can be serialized for saving as a value @@ -350,29 +383,32 @@ + (BOOL)canBeSerializedAsValue:(id)value // @param saved A set of objects that we can assume will have been saved. // @param error The reason why it can't be serialized. - (BOOL)canBeSerializedAfterSaving:(NSMutableArray *)saved withCurrentUser:(PFUser *)user error:(NSError **)error { + NSDictionary *dictionaryRepresentationCopy; @synchronized (lock) { - // This method is only used for batching sets of objects for saveAll - // and when saving children automatically. Since it's only used to - // determine whether or not save should be called on them, it only - // needs to examine their current values, so we use estimatedData. - if (![[self class] canBeSerializedAsValue:_estimatedData.dictionaryRepresentation - afterSaving:saved - error:error]) { - return NO; - } + dictionaryRepresentationCopy = [_estimatedData.dictionaryRepresentation copy]; + } - if ([self isDataAvailableForKey:@"ACL"] && - [[self ACLWithoutCopying] hasUnresolvedUser] && - ![saved containsObject:user]) { - if (error) { - *error = [PFErrorUtilities errorWithCode:kPFErrorInvalidACL - message:@"User associated with ACL must be signed up."]; - } - return NO; - } + // This method is only used for batching sets of objects for saveAll + // and when saving children automatically. Since it's only used to + // determine whether or not save should be called on them, it only + // needs to examine their current values, so we use estimatedData. + if (![[self class] canBeSerializedAsValue:dictionaryRepresentationCopy + afterSaving:saved + error:error]) { + return NO; + } - return YES; + if ([self isDataAvailableForKey:@"ACL"] && + [[self ACLWithoutCopying] hasUnresolvedUser] && + ![saved containsObject:user]) { + if (error) { + *error = [PFErrorUtilities errorWithCode:kPFErrorInvalidACL + message:@"User associated with ACL must be signed up."]; + } + return NO; } + + return YES; } // This saves all of the objects and files reachable from the given object. @@ -381,7 +417,10 @@ - (BOOL)canBeSerializedAfterSaving:(NSMutableArray *)saved withCurrentUser:(PFUs + (BFTask *)_deepSaveAsyncChildrenOfObject:(id)object withCurrentUser:(PFUser *)currentUser sessionToken:(NSString *)sessionToken { NSMutableSet *uniqueObjects = [NSMutableSet set]; NSMutableSet *uniqueFiles = [NSMutableSet set]; - [self collectDirtyChildren:object children:uniqueObjects files:uniqueFiles currentUser:currentUser]; + NSError *error; + if (![self collectDirtyChildren:object children:uniqueObjects files:uniqueFiles currentUser:currentUser error:&error]) { + return [BFTask taskWithError:error]; + } // Remove object from the queue of objects to save as this method should only save children. if ([object isKindOfClass:[PFObject class]]) { [uniqueObjects removeObject:object]; @@ -420,12 +459,10 @@ + (BFTask *)_deepSaveAsyncChildrenOfObject:(id)object withCurrentUser:(PFUser *) } remaining = nextBatch; - if (current.count == 0) { - // We do cycle-detection when building the list of objects passed to this - // function, so this should never get called. But we should check for it - // anyway, so that we get an exception instead of an infinite loop. - PFConsistencyAssertionFailure(@"Unable to save a PFObject with a relation to a cycle."); - } + // We do cycle-detection when building the list of objects passed to this + // function, so this should never get called. But we should check for it + // anyway, so that we get an exception instead of an infinite loop. + PFPreconditionWithTask(current.count != 0, @"Unable to save a PFObject with a relation to a cycle."); // If a lazy user is one of the objects in the array, resolve its laziness now and // remove it from the list of things to save. @@ -461,19 +498,27 @@ + (BFTask *)_deepSaveAsyncChildrenOfObject:(id)object withCurrentUser:(PFUser *) PFRESTCommand *command = nil; @synchronized ([object lock]) { [object _objectWillSave]; - [object _checkSaveParametersWithCurrentUser:currentUser]; + NSError *error; + if (![object _checkSaveParametersWithCurrentUser:currentUser error:&error]) { + return [BFTask taskWithError:error]; + } command = [object _constructSaveCommandForChanges:[object unsavedChanges] sessionToken:sessionToken - objectEncoder:[PFPointerObjectEncoder objectEncoder]]; + objectEncoder:[PFPointerObjectEncoder objectEncoder] + error:&error]; + PFPreconditionReturnFailedTask(command, error); [object startSave]; } [commands addObject:command]; } id commandRunner = [Parse _currentManager].commandRunner; + NSError *error; PFRESTCommand *batchCommand = [PFRESTObjectBatchCommand batchCommandWithCommands:commands sessionToken:sessionToken - serverURL:commandRunner.serverURL]; + serverURL:commandRunner.serverURL + error:&error]; + PFPreconditionReturnFailedTask(batchCommand, error); return [[commandRunner runCommandAsync:batchCommand withOptions:0] continueAsyncWithBlock:^id(BFTask *commandRunnerTask) { NSArray *results = [commandRunnerTask.result result]; @@ -545,7 +590,11 @@ + (BFTask *)_enqueueSaveEventuallyChildrenOfObject:(PFObject *)object currentUse return [BFTask taskFromExecutor:[BFExecutor defaultExecutor] withBlock:^id{ NSMutableSet *uniqueObjects = [NSMutableSet set]; NSMutableSet *uniqueFiles = [NSMutableSet set]; - [self collectDirtyChildren:object children:uniqueObjects files:uniqueFiles currentUser:currentUser]; + NSError *error; + if (![self collectDirtyChildren:object children:uniqueObjects files:uniqueFiles currentUser:currentUser error:&error]) { + return [BFTask taskWithError:error]; + } + for (PFFile *file in uniqueFiles) { if (!file.url) { NSError *error = [PFErrorUtilities errorWithCode:kPFErrorUnsavedFile @@ -574,12 +623,10 @@ + (BFTask *)_enqueueSaveEventuallyChildrenOfObject:(PFObject *)object currentUse } remaining = nextBatch; - if (current.count == 0) { - // We do cycle-detection when building the list of objects passed to this - // function, so this should never get called. But we should check for it - // anyway, so that we get an exception instead of an infinite loop. - PFConsistencyAssertionFailure(@"Unable to save a PFObject with a relation to a cycle."); - } + // We do cycle-detection when building the list of objects passed to this + // function, so this should never get called. But we should check for it + // anyway, so that we get an exception instead of an infinite loop. + PFPreconditionWithTask(current.count != 0, @"Unable to save a PFObject with a relation to a cycle."); // If a lazy user is one of the objects in the array, resolve its laziness now and // remove it from the list of things to save. @@ -710,8 +757,8 @@ - (BOOL)isDataAvailableForKey:(NSString *)key { ///-------------------------------------- // Validations that are done on save. For now, there is nothing. -- (void)_checkSaveParametersWithCurrentUser:(PFUser *)currentUser { - return; +- (BOOL)_checkSaveParametersWithCurrentUser:(PFUser *)currentUser error:(NSError **) error { + return YES; } /** @@ -737,22 +784,21 @@ - (NSString *)getOrCreateLocalId { return self.localId; } -- (void)resolveLocalId { +- (BOOL)resolveLocalId:(NSError *__autoreleasing*)error { @synchronized (lock) { - PFConsistencyAssert(self.localId, @"Tried to resolve a localId for an object with no localId. (%@)", self.parseClassName); + PFPreconditionBailAndSetError(self.localId, error, NO , @"Tried to resolve a localId for an object with no localId. (%@)", self.parseClassName); NSString *newObjectId = [[Parse _currentManager].coreManager.objectLocalIdStore objectIdForLocalId:self.localId]; // If we are resolving local ids, then this object is about to go over the network. // But if it has local ids that haven't been resolved yet, then that's not going to // be possible. - if (!newObjectId) { - PFConsistencyAssertionFailure(@"Tried to save an object with a pointer to a new, unsaved object. (%@)", self.parseClassName); - } + PFPreconditionBailAndSetError(newObjectId, error, NO , @"Tried to save an object with a pointer to a new, unsaved object. (%@)", self.parseClassName); // Nil out the localId so that the new objectId won't be saved back to the PFObjectLocalIdStore. self.localId = nil; self.objectId = newObjectId; } + return YES; } + (id)_objectFromDictionary:(NSDictionary *)dictionary @@ -867,7 +913,8 @@ + (BFTask *)_migrateObjectInBackgroundFromFile:(NSString *)fileName Encodes parse object into NSDictionary suitable for persisting into LDS. */ - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder - operationSetUUIDs:(NSArray **)operationSetUUIDs { + operationSetUUIDs:(NSArray **)operationSetUUIDs + error:(NSError **)error { NSArray *operationQueue = nil; PFObjectState *state = nil; NSUInteger deletingEventuallyCount = 0; @@ -881,15 +928,17 @@ - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder operationSetUUIDs:operationSetUUIDs state:state operationSetQueue:operationQueue - deletingEventuallyCount:deletingEventuallyCount]; + deletingEventuallyCount:deletingEventuallyCount + error:error]; } - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder operationSetUUIDs:(NSArray **)operationSetUUIDs state:(PFObjectState *)state operationSetQueue:(NSArray *)queue - deletingEventuallyCount:(NSUInteger)deletingEventuallyCount { - NSMutableDictionary *result = [[state dictionaryRepresentationWithObjectEncoder:objectEncoder] mutableCopy]; + deletingEventuallyCount:(NSUInteger)deletingEventuallyCount + error:(NSError **)error { + NSMutableDictionary *result = [[state dictionaryRepresentationWithObjectEncoder:objectEncoder error:error] mutableCopy]; result[PFObjectClassNameRESTKey] = state.parseClassName; result[PFObjectCompleteRESTKey] = @(state.complete); @@ -901,8 +950,11 @@ - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder NSMutableArray *mutableOperationSetUUIDs = [NSMutableArray array]; for (PFOperationSet *operation in queue) { NSArray *ooSetUUIDs = nil; - [operations addObject:[operation RESTDictionaryUsingObjectEncoder:objectEncoder - operationSetUUIDs:&ooSetUUIDs]]; + id operationDict = [operation RESTDictionaryUsingObjectEncoder:objectEncoder + operationSetUUIDs:&ooSetUUIDs + error:error]; + PFPreconditionBailOnError(operation, error, nil); + [operations addObject:operationDict]; [mutableOperationSetUUIDs addObjectsFromArray:ooSetUUIDs]; } @@ -912,7 +964,7 @@ - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder return result; } -- (void)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *)decoder { +- (BOOL)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *)decoder error:(NSError **)error { @synchronized (lock) { BOOL mergeServerData = NO; @@ -927,6 +979,8 @@ - (void)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *) } else if (!state.updatedAt) { mergeServerData = YES; } + __block BOOL hasFailed = NO; + __block NSError* remoteOpSetError = nil; [object enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if ([key isEqualToString:PFObjectOperationsRESTKey]) { PFOperationSet *remoteOperationSet = nil; @@ -970,7 +1024,13 @@ - (void)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *) [localOperationSet.updatedAt compare:remoteOperationSet.updatedAt] != NSOrderedAscending) { [localOperationSet mergeOperationSet:remoteOperationSet]; } else { - PFConsistencyAssert(remoteOperationSet, @"'remoteOperationSet' should not be nil."); + if (!remoteOperationSet) { + NSString *message = [NSString stringWithFormat:@"'remoteOperationSet' should not be nil in object of class %@", self.parseClassName]; + remoteOpSetError = [PFErrorUtilities errorWithCode:-1 message:message]; + hasFailed = YES; + *stop = YES; + return; + } NSUInteger index = [operationSetQueue indexOfObject:localOperationSet]; [remoteOperationSet mergeOperationSet:localOperationSet]; operationSetQueue[index] = remoteOperationSet; @@ -1022,6 +1082,10 @@ - (void)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *) id decodedObject = [decoder decodeObject:obj]; [state setServerDataObject:decodedObject forKey:key]; }]; + if (hasFailed && error) { + *error = remoteOpSetError; + return NO; + } if (state.updatedAt == nil && state.createdAt != nil) { state.updatedAt = state.createdAt; } @@ -1038,6 +1102,7 @@ - (void)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *) } } [self rebuildEstimatedData]; + return YES; } } @@ -1078,14 +1143,22 @@ - (BFTask *)_enqueueSaveEventuallyWithChildren:(BOOL)saveChildren { PFOperationSet *changes = [self unsavedChanges]; changes.saveEventually = YES; [self startSave]; - [self _checkSaveParametersWithCurrentUser:currentUser]; - PFRESTCommand *command = [self _constructSaveCommandForChanges:changes - sessionToken:sessionToken - objectEncoder:[PFPointerOrLocalIdObjectEncoder objectEncoder]]; - - // Enqueue the eventually operation! - saveTask = [[Parse _currentManager].eventuallyQueue enqueueCommandInBackground:command withObject:self]; - [self _enqueueSaveEventuallyOperationAsync:changes]; + NSError *error; + if (![self _checkSaveParametersWithCurrentUser:currentUser error:&error]) { + saveTask = [BFTask taskWithError:error]; + } else { + PFRESTCommand *command = [self _constructSaveCommandForChanges:changes + sessionToken:sessionToken + objectEncoder:[PFPointerOrLocalIdObjectEncoder objectEncoder] + error:&error]; + if (!command) { + saveTask = [BFTask taskWithError:error]; + } else { + // Enqueue the eventually operation! + saveTask = [[Parse _currentManager].eventuallyQueue enqueueCommandInBackground:command withObject:self]; + [self _enqueueSaveEventuallyOperationAsync:changes]; + } + } } saveTask = [saveTask continueWithBlock:^id(BFTask *task) { @try { @@ -1133,13 +1206,14 @@ - (BFTask *)_enqueueSaveEventuallyOperationAsync:(PFOperationSet *)operationSet ///-------------------------------------- - (NSMutableDictionary *)_convertToDictionaryForSaving:(PFOperationSet *)changes - withObjectEncoder:(PFEncoder *)encoder { + withObjectEncoder:(PFEncoder *)encoder + error:(NSError **)error { @synchronized (lock) { NSMutableDictionary *serialized = [NSMutableDictionary dictionary]; [changes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { serialized[key] = obj; }]; - return [encoder encodeObject:serialized]; + return [encoder encodeObject:serialized error:error]; } } @@ -1391,10 +1465,15 @@ - (BFTask *)saveAsync:(BFTask *)toAwait { return childrenTask; } return [[childrenTask continueWithSuccessBlock:^id(BFTask *task) { - [self _checkSaveParametersWithCurrentUser:currentUser]; + NSError *error; + if (![self _checkSaveParametersWithCurrentUser:currentUser error:&error]) { + return [BFTask taskWithError:error]; + } PFRESTCommand *command = [self _constructSaveCommandForChanges:changes sessionToken:sessionToken - objectEncoder:[PFPointerObjectEncoder objectEncoder]]; + objectEncoder:[PFPointerObjectEncoder objectEncoder] + error:&error]; + PFPreconditionReturnFailedTask(command, error); return [[Parse _currentManager].commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueAsyncWithBlock:^id(BFTask *task) { @@ -1437,11 +1516,13 @@ - (BFTask *)deleteAsync:(BFTask *)toAwait { #pragma mark - Command constructors ///-------------------------------------- -- (PFRESTCommand *)_constructSaveCommandForChanges:(PFOperationSet *)changes +- (nullable PFRESTCommand *)_constructSaveCommandForChanges:(PFOperationSet *)changes sessionToken:(NSString *)sessionToken - objectEncoder:(PFEncoder *)encoder { + objectEncoder:(PFEncoder *)encoder + error:(NSError **)error { @synchronized (lock) { - NSDictionary *parameters = [self _convertToDictionaryForSaving:changes withObjectEncoder:encoder]; + NSDictionary *parameters = [self _convertToDictionaryForSaving:changes withObjectEncoder:encoder error:error]; + PFPreconditionBailOnError(parameters, error, nil); if (self._state.objectId) { return [PFRESTObjectCommand updateObjectCommandForObjectState:self._state @@ -1542,19 +1623,20 @@ - (BOOL)needsDefaultACL { - (NSDictionary *)_collectFetchedObjects { NSMutableDictionary *fetchedObjects = [NSMutableDictionary dictionary]; + NSDictionary *dictionary; @synchronized (lock) { - NSDictionary *dictionary = _estimatedData.dictionaryRepresentation; - [PFInternalUtils traverseObject:dictionary usingBlock:^id(id obj) { - if ([obj isKindOfClass:[PFObject class]]) { - PFObject *object = obj; - NSString *objectId = object.objectId; - if (objectId && object.dataAvailable) { - fetchedObjects[objectId] = object; - } + dictionary = [_estimatedData.dictionaryRepresentation copy]; + } + [PFInternalUtils traverseObject:dictionary usingBlock:^id(id obj) { + if ([obj isKindOfClass:[PFObject class]]) { + PFObject *object = obj; + NSString *objectId = object.objectId; + if (objectId && object.dataAvailable) { + fetchedObjects[objectId] = object; } - return obj; - }]; - } + } + return obj; + }]; return fetchedObjects; } @@ -1771,7 +1853,7 @@ - (void)_notifyObjectIdChangedFrom:(NSString *)fromObjectId toObjectId:(NSString [store updateObjectIdForObject:self oldObjectId:fromObjectId newObjectId:toObjectId]; } if (self.localId) { - [[Parse _currentManager].coreManager.objectLocalIdStore setObjectId:toObjectId forLocalId:self.localId]; + [[Parse _currentManager].coreManager.objectLocalIdStore setObjectId:toObjectId forLocalId:self.localId error:nil]; self.localId = nil; } } diff --git a/Parse/Parse/PFPolygon.m b/Parse/Parse/PFPolygon.m index 8870c9e4c..d9a8509ce 100644 --- a/Parse/Parse/PFPolygon.m +++ b/Parse/Parse/PFPolygon.m @@ -104,7 +104,7 @@ - (BOOL)containsPoint:(PFGeoPoint *)point { static NSString *const PFPolygonCodingTypeKey = @"__type"; static NSString *const PFPolygonCodingCoordinatesKey = @"coordinates"; -- (NSDictionary *)encodeIntoDictionary { +- (NSDictionary *)encodeIntoDictionary:(NSError **)error { return @{ PFPolygonCodingTypeKey : @"Polygon", PFPolygonCodingCoordinatesKey : self.coordinates @@ -173,7 +173,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder { } - (void)encodeWithCoder:(NSCoder *)coder { - NSDictionary *dictionary = [self encodeIntoDictionary]; + NSDictionary *dictionary = [self encodeIntoDictionary:nil]; [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [coder encodeObject:obj forKey:key]; }]; diff --git a/Parse/Parse/PFRelation.m b/Parse/Parse/PFRelation.m index 8e0acdeb4..c391eee95 100644 --- a/Parse/Parse/PFRelation.m +++ b/Parse/Parse/PFRelation.m @@ -192,12 +192,15 @@ - (void)removeObject:(PFObject *)object { }); } -- (NSDictionary *)encodeIntoDictionary { +- (nullable NSDictionary *)encodeIntoDictionary:(NSError **)error { PFRelationState *state = [self.state copy]; NSMutableArray *encodedObjects = [NSMutableArray arrayWithCapacity:state.knownObjects.count]; - for (PFObject *knownObject in state.knownObjects) { - [encodedObjects addObject:[[PFPointerObjectEncoder objectEncoder] encodeObject:knownObject]]; + id result = [[PFPointerObjectEncoder objectEncoder] encodeObject:knownObject error:error]; + if (!result) { + return nil; + } + [encodedObjects addObject:result]; } return @{ diff --git a/Parse/Parse/PFUser.m b/Parse/Parse/PFUser.m index 3953217e1..4f472ff94 100644 --- a/Parse/Parse/PFUser.m +++ b/Parse/Parse/PFUser.m @@ -134,15 +134,20 @@ + (void)_assertValidInstanceClassName:(NSString *)className { } // Checks the properties on the object before saving. -- (void)_checkSaveParametersWithCurrentUser:(PFUser *)currentUser { +- (BOOL)_checkSaveParametersWithCurrentUser:(PFUser *)currentUser error:(NSError **)error { @synchronized([self lock]) { - PFConsistencyAssert(self.objectId || self._lazy, - @"User cannot be saved unless they are already signed up. Call signUp first."); - - PFConsistencyAssert([self _isAuthenticatedWithCurrentUser:currentUser] || - [self.objectId isEqualToString:currentUser.objectId], - @"User cannot be saved unless they have been authenticated via logIn or signUp", nil); + PFPreconditionBailAndSetError(self.objectId || self._lazy, + error, + NO, + @"User cannot be saved unless they are already signed up. Call signUp first."); + + PFPreconditionBailAndSetError([self _isAuthenticatedWithCurrentUser:currentUser] || + [self.objectId isEqualToString:currentUser.objectId], + error, + NO, + @"User cannot be saved unless they have been authenticated via logIn or signUp"); } + return YES; } // Checks the properties on the object before signUp. @@ -169,9 +174,10 @@ - (BFTask *)_validateSignUpAsync { } - (NSMutableDictionary *)_convertToDictionaryForSaving:(PFOperationSet *)changes - withObjectEncoder:(PFEncoder *)encoder { + withObjectEncoder:(PFEncoder *)encoder + error:(NSError **)error { @synchronized([self lock]) { - NSMutableDictionary *serialized = [super _convertToDictionaryForSaving:changes withObjectEncoder:encoder]; + NSMutableDictionary *serialized = [super _convertToDictionaryForSaving:changes withObjectEncoder:encoder error:error]; if (self.authData.count > 0) { serialized[PFUserAuthDataRESTKey] = [self.authData copy]; } @@ -196,13 +202,15 @@ - (BFTask *)handleSaveResultAsync:(NSDictionary *)result { #pragma mark - Sign Up ///-------------------------------------- -- (PFRESTCommand *)_currentSignUpCommandForChanges:(PFOperationSet *)changes { +- (nullable PFRESTCommand *)_currentSignUpCommandForChanges:(PFOperationSet *)changes error:(NSError **)error { @synchronized([self lock]) { NSDictionary *parameters = [self _convertToDictionaryForSaving:changes - withObjectEncoder:[PFPointerObjectEncoder objectEncoder]]; + withObjectEncoder:[PFPointerObjectEncoder objectEncoder] error:error]; + PFPreconditionBailOnError(parameters, error, nil); return [PFRESTUserCommand signUpUserCommandWithParameters:parameters revocableSession:[[self class] _isRevocableSessionEnabled] - sessionToken:self.sessionToken]; + sessionToken:self.sessionToken + error:error]; } } @@ -211,13 +219,15 @@ - (PFRESTCommand *)_currentSignUpCommandForChanges:(PFOperationSet *)changes { ///-------------------------------------- // Constructs the command for user_signup_or_login. This is used for Facebook, Twitter, and other linking services. -- (PFRESTCommand *)_currentServiceLoginCommandForChanges:(PFOperationSet *)changes { +- (PFRESTCommand *)_currentServiceLoginCommandForChanges:(PFOperationSet *)changes error:(NSError **)error { @synchronized([self lock]) { NSDictionary *parameters = [self _convertToDictionaryForSaving:changes - withObjectEncoder:[PFPointerObjectEncoder objectEncoder]]; + withObjectEncoder:[PFPointerObjectEncoder objectEncoder] error:error]; + PFPreconditionBailOnError(parameters, error, nil); return [PFRESTUserCommand serviceLoginUserCommandWithParameters:parameters revocableSession:[[self class] _isRevocableSessionEnabled] - sessionToken:self.sessionToken]; + sessionToken:self.sessionToken + error:error]; } } @@ -418,8 +428,10 @@ - (BFTask *)resolveLazinessAsync:(BFTask *)toAwait { }]; } + NSError *error; // Otherwise, treat this as a SignUpOrLogIn - PFRESTCommand *command = [self _currentServiceLoginCommandForChanges:[self unsavedChanges]]; + PFRESTCommand *command = [self _currentServiceLoginCommandForChanges:[self unsavedChanges] error:&error]; + PFPreconditionReturnFailedTask(command, error); [self startSave]; return [[toAwait continueAsyncWithBlock:^id(BFTask *task) { @@ -497,7 +509,7 @@ - (BFTask *)signUpAsync:(BFTask *)toAwait { // self doesn't have any outstanding saves, so we can safely merge its operations // into the current user. - PFConsistencyAssert(!self._current, @"Attempt to merge currentUser with itself."); + PFPreconditionWithTask(!self._current, @"Attempt to merge currentUser with itself."); @synchronized ([currentUser lock]) { NSString *oldUsername = [currentUser.username copy]; @@ -557,7 +569,9 @@ - (BFTask *)signUpAsync:(BFTask *)toAwait { }] continueWithSuccessBlock:^id(BFTask *task) { // We need to construct the signup command lazily, because saving the children // may change the way the object itself is serialized. - PFRESTCommand *command = [self _currentSignUpCommandForChanges:changes]; + NSError *error; + PFRESTCommand *command = [self _currentSignUpCommandForChanges:changes error:&error]; + PFPreconditionReturnFailedTask(command, error); return [[Parse _currentManager].commandRunner runCommandAsync:command withOptions:PFCommandRunningOptionRetryIfFailed]; }] continueWithBlock:^id(BFTask *task) { @@ -600,7 +614,8 @@ - (void)restoreAnonymity:(id)anonymousData { - (PFRESTCommand *)_constructSaveCommandForChanges:(PFOperationSet *)changes sessionToken:(NSString *)token - objectEncoder:(PFEncoder *)encoder { + objectEncoder:(PFEncoder *)encoder + error:(NSError **)error { // If we are curent user - use the latest available session token, as it might have been changed since // this command was enqueued. if (self._current) { @@ -608,14 +623,15 @@ - (PFRESTCommand *)_constructSaveCommandForChanges:(PFOperationSet *)changes } return [super _constructSaveCommandForChanges:changes sessionToken:token - objectEncoder:encoder]; + objectEncoder:encoder + error:error]; } ///-------------------------------------- #pragma mark - REST operations ///-------------------------------------- -- (void)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *)decoder { +- (BOOL)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *)decoder error:(NSError **)error { @synchronized([self lock]) { NSMutableDictionary *restDictionary = [object mutableCopy]; @@ -640,7 +656,7 @@ - (void)mergeFromRESTDictionary:(NSDictionary *)object withDecoder:(PFDecoder *) self._state = state; - [super mergeFromRESTDictionary:restDictionary withDecoder:decoder]; + return [super mergeFromRESTDictionary:restDictionary withDecoder:decoder error:error]; } } @@ -648,7 +664,8 @@ - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder operationSetUUIDs:(NSArray **)operationSetUUIDs state:(PFObjectState *)state operationSetQueue:(NSArray *)queue - deletingEventuallyCount:(NSUInteger)deletingEventuallyCount { + deletingEventuallyCount:(NSUInteger)deletingEventuallyCount + error:(NSError **)error { NSMutableArray *cleanQueue = [queue mutableCopy]; [queue enumerateObjectsUsingBlock:^(PFOperationSet *operationSet, NSUInteger idx, BOOL *stop) { // Remove operations for `password` field, to not let it persist to LDS. @@ -663,7 +680,8 @@ - (NSDictionary *)RESTDictionaryWithObjectEncoder:(PFEncoder *)objectEncoder operationSetUUIDs:operationSetUUIDs state:state operationSetQueue:cleanQueue - deletingEventuallyCount:deletingEventuallyCount]; + deletingEventuallyCount:deletingEventuallyCount + error:error]; } ///-------------------------------------- @@ -723,7 +741,9 @@ - (BFTask *)_upgradeToRevocableSessionInBackground { return self; } - PFRESTCommand *command = [PFRESTUserCommand upgradeToRevocableSessionCommandWithSessionToken:token]; + NSError *error; + PFRESTCommand *command = [PFRESTUserCommand upgradeToRevocableSessionCommandWithSessionToken:token error:&error]; + PFPreconditionReturnFailedTask(command, error); return [[[Parse _currentManager].commandRunner runCommandAsync:command withOptions:0] continueWithSuccessBlock:^id(BFTask *task) { NSDictionary *dictionary = [task.result result]; diff --git a/Parse/Parse/ParseClientConfiguration.m b/Parse/Parse/ParseClientConfiguration.m index e27b401db..c2be76c78 100644 --- a/Parse/Parse/ParseClientConfiguration.m +++ b/Parse/Parse/ParseClientConfiguration.m @@ -45,7 +45,7 @@ - (instancetype)initWithBlock:(void (^)(id))con configurationBlock(self); - PFConsistencyAssert(self.applicationId.length, @"`applicationId` should not be nil."); + PFParameterAssert(self.applicationId.length, @"`applicationId` should not be nil."); return self; } diff --git a/Parse/Parse/Resources/Parse-OSX.Info.plist b/Parse/Parse/Resources/Parse-OSX.Info.plist index bf70b5105..758e42eb6 100644 --- a/Parse/Parse/Resources/Parse-OSX.Info.plist +++ b/Parse/Parse/Resources/Parse-OSX.Info.plist @@ -13,10 +13,10 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 diff --git a/Parse/Parse/Resources/Parse-iOS.Info.plist b/Parse/Parse/Resources/Parse-iOS.Info.plist index f8a0bda0a..ae526b097 100644 --- a/Parse/Parse/Resources/Parse-iOS.Info.plist +++ b/Parse/Parse/Resources/Parse-iOS.Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -22,7 +22,7 @@ iPhoneOS CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 MinimumOSVersion 6.0 diff --git a/Parse/Parse/Resources/Parse-tvOS.Info.plist b/Parse/Parse/Resources/Parse-tvOS.Info.plist index 1dee6e1de..7bdfabf95 100644 --- a/Parse/Parse/Resources/Parse-tvOS.Info.plist +++ b/Parse/Parse/Resources/Parse-tvOS.Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 NSPrincipalClass diff --git a/Parse/Parse/Resources/Parse-watchOS.Info.plist b/Parse/Parse/Resources/Parse-watchOS.Info.plist index 1dee6e1de..7bdfabf95 100644 --- a/Parse/Parse/Resources/Parse-watchOS.Info.plist +++ b/Parse/Parse/Resources/Parse-watchOS.Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 NSPrincipalClass diff --git a/Parse/Tests/Other/Swift/SwiftSubclass.swift b/Parse/Tests/Other/Swift/SwiftSubclass.swift index 59dedeed1..907c4f767 100644 --- a/Parse/Tests/Other/Swift/SwiftSubclass.swift +++ b/Parse/Tests/Other/Swift/SwiftSubclass.swift @@ -29,4 +29,12 @@ public class SwiftSubclass: PFObject, PFSubclassing { func test_validateSwiftImport() { let _ = SwiftSubclass(withoutDataWithObjectId: "") } + + func test_properACLSetters() { + let acl = PFACL() + acl.hasPublicReadAccess = true + acl.hasPublicWriteAccess = true + _ = acl.hasPublicWriteAccess + _ = acl.hasPublicReadAccess + } } diff --git a/Parse/Tests/Unit/CloudCommandTests.m b/Parse/Tests/Unit/CloudCommandTests.m index 45909dc41..9234ebb67 100644 --- a/Parse/Tests/Unit/CloudCommandTests.m +++ b/Parse/Tests/Unit/CloudCommandTests.m @@ -20,7 +20,8 @@ @implementation CloudCommandTests - (void)testFunctionCommand { PFRESTCloudCommand *command = [PFRESTCloudCommand commandForFunction:@"a" withParameters:nil - sessionToken:nil]; + sessionToken:nil + error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"functions/a"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -29,7 +30,8 @@ - (void)testFunctionCommand { command = [PFRESTCloudCommand commandForFunction:@"a" withParameters:@{ @"b" : @"c" } - sessionToken:@"yarr"]; + sessionToken:@"yarr" + error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"functions/a"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); diff --git a/Parse/Tests/Unit/CommandURLRequestConstructorTests.m b/Parse/Tests/Unit/CommandURLRequestConstructorTests.m index a9bbdc67a..33040187c 100644 --- a/Parse/Tests/Unit/CommandURLRequestConstructorTests.m +++ b/Parse/Tests/Unit/CommandURLRequestConstructorTests.m @@ -58,7 +58,8 @@ - (void)testDataURLRequest { PFRESTCommand *command = [PFRESTCommand commandWithHTTPPath:@"yolo" httpMethod:PFHTTPRequestMethodPOST parameters:@{ @"a" : @"b" } - sessionToken:@"yarr"]; + sessionToken:@"yarr" + error:nil]; command.additionalRequestHeaders = @{ @"CustomHeader" : @"CustomValue" }; NSURLRequest *request = [[constructor getDataURLRequestAsyncForCommand:command] waitForResult:nil]; @@ -79,28 +80,32 @@ - (void)testDataURLRequestMethodOverride { PFRESTCommand *command = [PFRESTCommand commandWithHTTPPath:@"yolo" httpMethod:PFHTTPRequestMethodGET parameters:@{ @"a" : @"b" } - sessionToken:@"yarr"]; + sessionToken:@"yarr" + error:nil]; NSURLRequest *request = [[constructor getDataURLRequestAsyncForCommand:command] waitForResult:nil]; XCTAssertEqualObjects(request.HTTPMethod, @"POST"); command = [PFRESTCommand commandWithHTTPPath:@"yolo" httpMethod:PFHTTPRequestMethodHEAD parameters:@{ @"a" : @"b" } - sessionToken:@"yarr"]; + sessionToken:@"yarr" + error:nil]; request = [[constructor getDataURLRequestAsyncForCommand:command] waitForResult:nil]; XCTAssertEqualObjects(request.HTTPMethod, @"POST"); command = [PFRESTCommand commandWithHTTPPath:@"yolo" httpMethod:PFHTTPRequestMethodGET parameters:@{ @"a" : @"b" } - sessionToken:@"yarr"]; + sessionToken:@"yarr" + error:nil]; request = [[constructor getDataURLRequestAsyncForCommand:command] waitForResult:nil]; XCTAssertEqualObjects(request.HTTPMethod, @"POST"); command = [PFRESTCommand commandWithHTTPPath:@"yolo" httpMethod:PFHTTPRequestMethodGET parameters:nil - sessionToken:@"yarr"]; + sessionToken:@"yarr" + error:nil]; request = [[constructor getDataURLRequestAsyncForCommand:command] waitForResult:nil]; XCTAssertEqualObjects(request.HTTPMethod, @"GET"); } @@ -113,7 +118,8 @@ - (void)testDataURLRequestBodyEncoding { PFRESTCommand *command = [PFRESTCommand commandWithHTTPPath:@"yolo" httpMethod:PFHTTPRequestMethodPOST parameters:@{ @"a" : @100500 } - sessionToken:@"yarr"]; + sessionToken:@"yarr" + error:nil]; NSURLRequest *request = [[constructor getDataURLRequestAsyncForCommand:command] waitForResult:nil]; id json = [NSJSONSerialization JSONObjectWithData:request.HTTPBody options:0 error:nil]; XCTAssertNotNil(json); @@ -128,7 +134,8 @@ - (void)testFileUploadURLRequest { PFRESTCommand *command = [PFRESTCommand commandWithHTTPPath:@"yolo" httpMethod:PFHTTPRequestMethodPOST parameters:@{ @"a" : @100500 } - sessionToken:@"yarr"]; + sessionToken:@"yarr" + error:nil]; NSURLRequest *request = [[constructor getFileUploadURLRequestAsyncForCommand:command withContentType:@"boom" contentSourceFilePath:@"/dev/null"] waitForResult:nil]; @@ -150,4 +157,23 @@ - (void)testDefaultURLRequestHeaders { XCTAssertNotNil(headers[PFCommandHeaderNameAppDisplayVersion]); } +- (void)testBailOnEncodingError { + id providerMock = [self mockedInstallationidentifierStoreProviderWithInstallationIdentifier:@"installationId"]; + NSURL *url = [NSURL URLWithString:@"https://parse.com/123"]; + PFCommandURLRequestConstructor *constructor = [PFCommandURLRequestConstructor constructorWithDataSource:providerMock serverURL:url]; + + PFRESTCommand *command = [PFRESTCommand commandWithHTTPPath:@"yolo" + httpMethod:PFHTTPRequestMethodPOST + parameters:@{ @"a" : [PFObject objectWithClassName:@"MyObject"] } + sessionToken:@"yarr" + error:nil]; + command.additionalRequestHeaders = @{ @"CustomHeader" : @"CustomValue" }; + NSError *error; + NSURLRequest *request = [[constructor getDataURLRequestAsyncForCommand:command] waitForResult:&error]; + XCTAssertNil(request); + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqualObjects(error.localizedDescription, @"Tried to save an object with a new, unsaved child."); +} + @end diff --git a/Parse/Tests/Unit/CommandUnitTests.m b/Parse/Tests/Unit/CommandUnitTests.m index 2acbbce29..dc3cdde32 100644 --- a/Parse/Tests/Unit/CommandUnitTests.m +++ b/Parse/Tests/Unit/CommandUnitTests.m @@ -50,7 +50,8 @@ - (void)testNoRetryOn400StatusCode { PFRESTCommand *command = [PFRESTCommand commandWithHTTPPath:@"login" httpMethod:PFHTTPRequestMethodPOST parameters:nil - sessionToken:nil]; + sessionToken:nil + error:nil]; NSError *error = nil; PFURLSessionCommandRunner *commandRunner = [PFURLSessionCommandRunner commandRunnerWithDataSource:[Parse _currentManager] @@ -80,7 +81,8 @@ - (void)testRetryOn500StatusCode { PFRESTCommand *command = [PFRESTCommand commandWithHTTPPath:@"login" httpMethod:PFHTTPRequestMethodPOST parameters:nil - sessionToken:nil]; + sessionToken:nil + error:nil]; NSError *error = nil; PFURLSessionCommandRunner *commandRunner = [PFURLSessionCommandRunner commandRunnerWithDataSource:[Parse _currentManager] @@ -107,7 +109,8 @@ - (void)testCacheKeysFromCommand { PFRESTCommand *orderedCommand = [PFRESTCommand commandWithHTTPPath:@"foo" httpMethod:PFHTTPRequestMethodGET parameters:orderedDict - sessionToken:nil]; + sessionToken:nil + error:nil]; NSMutableDictionary *reversedDict = [NSMutableDictionary dictionary]; for (int i = 30; i >= 1; --i) { @@ -117,7 +120,8 @@ - (void)testCacheKeysFromCommand { PFRESTCommand *reversedCommand = [PFRESTCommand commandWithHTTPPath:@"foo" httpMethod:PFHTTPRequestMethodGET parameters:reversedDict - sessionToken:nil]; + sessionToken:nil + error:nil]; XCTAssertEqualObjects(orderedCommand.cacheKey, reversedCommand.cacheKey, @"identifiers should be invariant to dictionary key orders"); diff --git a/Parse/Tests/Unit/FieldOperationTests.m b/Parse/Tests/Unit/FieldOperationTests.m index 0ce0defd0..f0fb50cbc 100644 --- a/Parse/Tests/Unit/FieldOperationTests.m +++ b/Parse/Tests/Unit/FieldOperationTests.m @@ -30,7 +30,7 @@ - (void)testFieldOperationConstructors { - (void)testFieldOperationEncoding { PFFieldOperation *operation = [[PFFieldOperation alloc] init]; - XCTAssertThrows([operation encodeWithObjectEncoder:[PFEncoder objectEncoder]]); + XCTAssertThrows([operation encodeWithObjectEncoder:[PFEncoder objectEncoder] error:nil]); } - (void)testFieldOperationMerge { @@ -82,10 +82,10 @@ - (void)testSetOperationMerge { - (void)testSetOperationEncoding { PFEncoder *encoder = PFStrictClassMock([PFEncoder class]); - OCMStub([encoder encodeObject:[OCMArg isEqual:@"yarr"]]).andReturn(@"yolo"); + OCMStub([encoder encodeObject:[OCMArg isEqual:@"yarr"] error:nil]).andReturn(@"yolo"); PFSetOperation *operation = [PFSetOperation setWithValue:@"yarr"]; - XCTAssertEqualObjects([operation encodeWithObjectEncoder:encoder], @"yolo"); + XCTAssertEqualObjects([operation encodeWithObjectEncoder:encoder error:nil], @"yolo"); } ///-------------------------------------- @@ -108,10 +108,10 @@ - (void)testDeleteOperationDescription { - (void)testDeleteOperationEncoding { PFDeleteOperation *operation = [PFDeleteOperation operation]; - NSDictionary *encoded = [operation encodeWithObjectEncoder:nil]; + NSDictionary *encoded = [operation encodeWithObjectEncoder:nil error:nil]; XCTAssertEqualObjects(encoded, @{ @"__op" : @"Delete" }); - encoded = [operation encodeWithObjectEncoder:[PFEncoder objectEncoder]]; + encoded = [operation encodeWithObjectEncoder:[PFEncoder objectEncoder] error:nil]; XCTAssertEqualObjects(encoded, @{ @"__op" : @"Delete" }); } @@ -153,10 +153,10 @@ - (void)testIncrementOperationEncoding { NSDictionary *properEncodedDictionary = @{ @"__op" : @"Increment", @"amount" : @100500 }; - NSDictionary *encoded = [operation encodeWithObjectEncoder:nil]; + NSDictionary *encoded = [operation encodeWithObjectEncoder:nil error:nil]; XCTAssertEqualObjects(encoded, properEncodedDictionary); - encoded = [operation encodeWithObjectEncoder:[PFEncoder objectEncoder]]; + encoded = [operation encodeWithObjectEncoder:[PFEncoder objectEncoder] error:nil]; XCTAssertEqualObjects(encoded, properEncodedDictionary); } @@ -200,11 +200,11 @@ - (void)testAddOperationDescription { - (void)testAddOperationEncoding { PFEncoder *encoder = PFStrictClassMock([PFEncoder class]); - OCMStub([encoder encodeObject:[OCMArg isEqual:@[ @"yarr" ]]]).andReturn(@"yolo"); + OCMStub([encoder encodeObject:[OCMArg isEqual:@[ @"yarr" ]] error:nil]).andReturn(@"yolo"); PFAddOperation *operation = [PFAddOperation addWithObjects:@[ @"yarr" ]]; - XCTAssertThrows([operation encodeWithObjectEncoder:nil]); - XCTAssertEqualObjects([operation encodeWithObjectEncoder:encoder], (@{ @"__op" : @"Add", + XCTAssertThrows([operation encodeWithObjectEncoder:nil error:nil]); + XCTAssertEqualObjects([operation encodeWithObjectEncoder:encoder error:nil], (@{ @"__op" : @"Add", @"objects" : @"yolo" })); } @@ -249,11 +249,11 @@ - (void)testAddUniqueOperationDescription { - (void)testAddUniqueOperationEncoding { PFEncoder *encoder = PFStrictClassMock([PFEncoder class]); - OCMStub([encoder encodeObject:[OCMArg isEqual:@[ @"yarr" ]]]).andReturn(@"yolo"); + OCMStub([encoder encodeObject:[OCMArg isEqual:@[ @"yarr" ]] error:nil]).andReturn(@"yolo"); PFAddUniqueOperation *operation = [PFAddUniqueOperation addUniqueWithObjects:@[ @"yarr" ]]; - XCTAssertThrows([operation encodeWithObjectEncoder:nil]); - XCTAssertEqualObjects([operation encodeWithObjectEncoder:encoder], (@{ @"__op" : @"AddUnique", + XCTAssertThrows([operation encodeWithObjectEncoder:nil error:nil]); + XCTAssertEqualObjects([operation encodeWithObjectEncoder:encoder error:nil], (@{ @"__op" : @"AddUnique", @"objects" : @"yolo" })); } @@ -298,11 +298,11 @@ - (void)testRemoveOperationDescription { - (void)testRemoveOperationEncoding { PFEncoder *encoder = PFStrictClassMock([PFEncoder class]); - OCMStub([encoder encodeObject:[OCMArg isEqual:@[ @"yarr" ]]]).andReturn(@"yolo"); + OCMStub([encoder encodeObject:[OCMArg isEqual:@[ @"yarr" ]] error:nil]).andReturn(@"yolo"); PFRemoveOperation *operation = [PFRemoveOperation removeWithObjects:@[ @"yarr" ]]; - XCTAssertThrows([operation encodeWithObjectEncoder:nil]); - XCTAssertEqualObjects([operation encodeWithObjectEncoder:encoder], (@{ @"__op" : @"Remove", + XCTAssertThrows([operation encodeWithObjectEncoder:nil error:nil]); + XCTAssertEqualObjects([operation encodeWithObjectEncoder:encoder error:nil], (@{ @"__op" : @"Remove", @"objects" : @"yolo" })); } @@ -362,28 +362,28 @@ - (void)testRelationOperationDescription { - (void)testRelationOperationEncoding { PFObject *object = [PFObject objectWithClassName:@"Yolo"]; PFEncoder *encoder = PFStrictClassMock([PFEncoder class]); - OCMStub([encoder encodeObject:OCMOCK_ANY]).andReturn(@"yolo"); + OCMStub([encoder encodeObject:OCMOCK_ANY error:nil]).andReturn(@"yolo"); PFRelationOperation *operation = [PFRelationOperation addRelationToObjects:@[ object, object ]]; - NSDictionary *encoded = [operation encodeWithObjectEncoder:encoder]; + NSDictionary *encoded = [operation encodeWithObjectEncoder:encoder error:nil]; XCTAssertEqualObjects(encoded, (@{ @"__op" : @"AddRelation", @"objects" : @[ @"yolo" ] })); operation = [PFRelationOperation removeRelationToObjects:@[ object, object ]]; - encoded = [operation encodeWithObjectEncoder:encoder]; + encoded = [operation encodeWithObjectEncoder:encoder error:nil]; XCTAssertEqualObjects(encoded, (@{ @"__op" : @"RemoveRelation", @"objects" : @[ @"yolo" ] })); PFObject *anotherObject = [PFObject objectWithClassName:@"Yolo"]; operation = (PFRelationOperation *)[operation mergeWithPrevious:[PFRelationOperation addRelationToObjects:@[ anotherObject ]]]; - encoded = [operation encodeWithObjectEncoder:encoder]; + encoded = [operation encodeWithObjectEncoder:encoder error:nil]; XCTAssertEqualObjects(encoded, (@{ @"__op" : @"Batch", @"ops" : @[ @{@"__op" : @"AddRelation", @"objects" : @[ @"yolo" ]}, @{@"__op" : @"RemoveRelation", @"objects" : @[ @"yolo" ]} ] })); - XCTAssertThrows([[PFRelationOperation addRelationToObjects:@[]] encodeWithObjectEncoder:encoder]); - XCTAssertThrows([[PFRelationOperation removeRelationToObjects:@[]] encodeWithObjectEncoder:encoder]); + XCTAssertThrows([[PFRelationOperation addRelationToObjects:@[]] encodeWithObjectEncoder:encoder error:nil]); + XCTAssertThrows([[PFRelationOperation removeRelationToObjects:@[]] encodeWithObjectEncoder:encoder error:nil]); } - (void)testRelationOperationMerge { diff --git a/Parse/Tests/Unit/GeoPointUnitTests.m b/Parse/Tests/Unit/GeoPointUnitTests.m index aaccb27a1..ac815b56a 100644 --- a/Parse/Tests/Unit/GeoPointUnitTests.m +++ b/Parse/Tests/Unit/GeoPointUnitTests.m @@ -42,7 +42,7 @@ - (void)testGeoPointFromLocation { - (void)testGeoPointDictionaryEncoding { PFGeoPoint *point = [PFGeoPoint geoPointWithLatitude:10 longitude:20]; - NSDictionary *dictionary = [point encodeIntoDictionary]; + NSDictionary *dictionary = [point encodeIntoDictionary:nil]; XCTAssertNotNil(dictionary); PFGeoPoint *pointFromDictionary = [PFGeoPoint geoPointWithDictionary:dictionary]; diff --git a/Parse/Tests/Unit/ObjectBatchCommandTests.m b/Parse/Tests/Unit/ObjectBatchCommandTests.m index c3c44bede..eb6706973 100644 --- a/Parse/Tests/Unit/ObjectBatchCommandTests.m +++ b/Parse/Tests/Unit/ObjectBatchCommandTests.m @@ -58,7 +58,8 @@ - (void)testBatchCommand { NSArray *commands = [self sampleObjectCommands]; PFRESTObjectBatchCommand *command = [PFRESTObjectBatchCommand batchCommandWithCommands:commands sessionToken:@"yolo" - serverURL:[NSURL URLWithString:@"https://api.parse.com/1"]]; + serverURL:[NSURL URLWithString:@"https://api.parse.com/1"] + error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"batch"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -76,10 +77,12 @@ - (void)testBatchCommandValidation { while (array.count < PFRESTObjectBatchCommandSubcommandsLimit) { [array addObjectsFromArray:[self sampleObjectCommands]]; } - + NSError *error; PFAssertThrowsInvalidArgumentException([PFRESTObjectBatchCommand batchCommandWithCommands:array sessionToken:@"a" - serverURL:[NSURL URLWithString:@"https://api.parse.com/1"]]); + serverURL:[NSURL URLWithString:@"https://api.parse.com/1"] + error:&error]); + XCTAssertNil(error); } @end diff --git a/Parse/Tests/Unit/ObjectLocalIdStoreTests.m b/Parse/Tests/Unit/ObjectLocalIdStoreTests.m index 44a76df3d..dd6c24280 100644 --- a/Parse/Tests/Unit/ObjectLocalIdStoreTests.m +++ b/Parse/Tests/Unit/ObjectLocalIdStoreTests.m @@ -59,41 +59,41 @@ - (void)testRetain { NSString *localId1 = [store createLocalId]; XCTAssertNotNil(localId1); - [store retainLocalIdOnDisk:localId1]; // refcount = 1 + [store retainLocalIdOnDisk:localId1 error:nil]; // refcount = 1 XCTAssertNil([store objectIdForLocalId:localId1]); NSString *localId2 = [store createLocalId]; XCTAssertNotNil(localId2); - [store retainLocalIdOnDisk:localId2]; // refcount = 1 + [store retainLocalIdOnDisk:localId2 error:nil]; // refcount = 1 XCTAssertNil([store objectIdForLocalId:localId2]); - [store retainLocalIdOnDisk:localId1]; // refcount = 2 + [store retainLocalIdOnDisk:localId1 error:nil]; // refcount = 2 XCTAssertNil([store objectIdForLocalId:localId1]); XCTAssertNil([store objectIdForLocalId:localId2]); - [store releaseLocalIdOnDisk:localId1]; // refcount = 1 + [store releaseLocalIdOnDisk:localId1 error:nil]; // refcount = 1 XCTAssertNil([store objectIdForLocalId:localId1]); XCTAssertNil([store objectIdForLocalId:localId2]); NSString *objectId1 = @"objectId1"; - [store setObjectId:objectId1 forLocalId:localId1]; + [store setObjectId:objectId1 forLocalId:localId1 error:nil]; XCTAssertEqualObjects(objectId1, [store objectIdForLocalId:localId1]); XCTAssertNil([store objectIdForLocalId:localId2]); - [store retainLocalIdOnDisk:localId1]; // refcount = 2 + [store retainLocalIdOnDisk:localId1 error:nil]; // refcount = 2 XCTAssertEqualObjects(objectId1, [store objectIdForLocalId:localId1]); XCTAssertNil([store objectIdForLocalId:localId2]); NSString *objectId2 = @"objectId2"; - [store setObjectId:objectId2 forLocalId:localId2]; + [store setObjectId:objectId2 forLocalId:localId2 error:nil]; XCTAssertEqualObjects(objectId1, [store objectIdForLocalId:localId1]); XCTAssertEqualObjects(objectId2, [store objectIdForLocalId:localId2]); - [store releaseLocalIdOnDisk:localId1]; // refcount = 1 + [store releaseLocalIdOnDisk:localId1 error:nil]; // refcount = 1 XCTAssertEqualObjects(objectId1, [store objectIdForLocalId:localId1]); XCTAssertEqualObjects(objectId2, [store objectIdForLocalId:localId2]); - [store releaseLocalIdOnDisk:localId1]; // refcount = 0 + [store releaseLocalIdOnDisk:localId1 error:nil]; // refcount = 0 XCTAssertEqualObjects(objectId1, [store objectIdForLocalId:localId1]); XCTAssertEqualObjects(objectId2, [store objectIdForLocalId:localId2]); @@ -101,7 +101,7 @@ - (void)testRetain { XCTAssertNil([store objectIdForLocalId:localId1]); XCTAssertEqualObjects(objectId2, [store objectIdForLocalId:localId2]); - [store releaseLocalIdOnDisk:localId2]; // refcount = 0 + [store releaseLocalIdOnDisk:localId2 error:nil]; // refcount = 0 XCTAssertNil([store objectIdForLocalId:localId1]); XCTAssertNil([store objectIdForLocalId:localId2]); @@ -120,8 +120,8 @@ - (void)testRetainAfterRelease { PFObjectLocalIdStore *store = [[PFObjectLocalIdStore alloc] initWithDataSource:dataSource]; NSString *localId = [store createLocalId]; - [store setObjectId:@"venus" forLocalId:localId]; - [store retainLocalIdOnDisk:localId]; + [store setObjectId:@"venus" forLocalId:localId error:nil]; + [store retainLocalIdOnDisk:localId error:nil]; [store clearInMemoryCache]; XCTAssertEqualObjects(@"venus", [store objectIdForLocalId:localId]); @@ -140,4 +140,19 @@ - (void)testLongSerialization { XCTAssertEqual(expected, actual, @"The number should be parsed correctly."); } +- (void)testInvalidLocalId { + id dataSource = [self mockedDataSource]; + PFFileManager *fileManager = dataSource.fileManager; + OCMStub([fileManager parseDataItemPathForPathComponent:[OCMArg isNotNil]]).andReturn(NSTemporaryDirectory()); + + PFObjectLocalIdStore *store = [[PFObjectLocalIdStore alloc] initWithDataSource:dataSource]; + NSError *error; + BOOL rval = [store retainLocalIdOnDisk:@"bad-local-id" error:&error]; + XCTAssertFalse(rval); + XCTAssertNotNil(error.domain); + XCTAssertEqual(error.domain, PFParseErrorDomain); + XCTAssertEqual(error.code, -1); + XCTAssertEqualObjects(error.localizedDescription, @"Tried to get invalid local id: \"bad-local-id\"."); +} + @end diff --git a/Parse/Tests/Unit/ObjectStateTests.m b/Parse/Tests/Unit/ObjectStateTests.m index baec00811..693a8a89b 100644 --- a/Parse/Tests/Unit/ObjectStateTests.m +++ b/Parse/Tests/Unit/ObjectStateTests.m @@ -191,7 +191,7 @@ - (void)testEncode { @"a": @"b" }; - NSDictionary *actual = [mutableState dictionaryRepresentationWithObjectEncoder:[PFEncoder objectEncoder]]; + NSDictionary *actual = [mutableState dictionaryRepresentationWithObjectEncoder:[PFEncoder objectEncoder] error:nil]; XCTAssertEqualObjects(actual, expected); } diff --git a/Parse/Tests/Unit/ObjectUnitTests.m b/Parse/Tests/Unit/ObjectUnitTests.m index c8babfd6d..324dadbf3 100644 --- a/Parse/Tests/Unit/ObjectUnitTests.m +++ b/Parse/Tests/Unit/ObjectUnitTests.m @@ -292,4 +292,40 @@ - (void)testRecursiveDirty { XCTAssertFalse(objectA.dirty); } +-(void)testSaveRelationToACycle { + PFObject *objectA = [PFObject objectWithClassName:@"A"]; + PFObject *objectB = [PFObject objectWithClassName:@"B"]; + objectA[@"B"] = objectB; + objectB[@"A"] = objectA; + NSError *error; + [objectA save:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqualObjects(error.localizedDescription, @"Found a circular dependency when saving."); +} +-(void)testSaveRelationToACycleInAnArray { + PFObject *objectA = [PFObject objectWithClassName:@"A"]; + PFObject *objectB = [PFObject objectWithClassName:@"B"]; + objectA[@"B"] = objectB; + objectB[@"A"] = @[objectA]; + NSError *error; + [objectA save:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqualObjects(error.localizedDescription, @"Found a circular dependency when saving."); +} + +-(void)testSaveRelationToACycleInANestedObject { + PFObject *objectA = [PFObject objectWithClassName:@"A"]; + PFObject *objectB = [PFObject objectWithClassName:@"B"]; + objectA[@"B"] = objectB; + objectB[@"A"] = @{@"a": objectA}; + NSError *error; + [objectA save:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqualObjects(error.localizedDescription, @"Found a circular dependency when saving."); +} + + @end diff --git a/Parse/Tests/Unit/OperationSetUnitTests.m b/Parse/Tests/Unit/OperationSetUnitTests.m index 91981e8e0..d41f94b7c 100644 --- a/Parse/Tests/Unit/OperationSetUnitTests.m +++ b/Parse/Tests/Unit/OperationSetUnitTests.m @@ -53,7 +53,8 @@ - (void)testOperationSetWithREST { // Use default PFEncoding NSArray *operationSetUUIDs = nil; NSDictionary *restified = [operation1 RESTDictionaryUsingObjectEncoder:[PFPointerObjectEncoder objectEncoder] - operationSetUUIDs:&operationSetUUIDs]; + operationSetUUIDs:&operationSetUUIDs + error:nil]; // Check returned operationSetUUIDs XCTAssertNotNil(operationSetUUIDs); PFAssertEqualInts(1, operationSetUUIDs.count); diff --git a/Parse/Tests/Unit/PolygonUnitTests.m b/Parse/Tests/Unit/PolygonUnitTests.m index 5164e57f0..72817ccf6 100644 --- a/Parse/Tests/Unit/PolygonUnitTests.m +++ b/Parse/Tests/Unit/PolygonUnitTests.m @@ -37,7 +37,7 @@ - (void)testPolygonFromCoordinates { - (void)testPolygonDictionaryEncoding { PFPolygon *polygon = [PFPolygon polygonWithCoordinates:_testPoints]; - NSDictionary *dictionary = [polygon encodeIntoDictionary]; + NSDictionary *dictionary = [polygon encodeIntoDictionary:nil]; XCTAssertNotNil(dictionary); PFPolygon *polygonFromDictionary = [PFPolygon polygonWithDictionary:dictionary]; @@ -49,7 +49,7 @@ - (void)testPolygonPFEncoding { PFPolygon *polygon = [PFPolygon polygonWithCoordinates:_testPoints]; PFEncoder *encoder = [[PFEncoder alloc] init]; - NSDictionary *dictionary = [encoder encodeObject:polygon]; + NSDictionary *dictionary = [encoder encodeObject:polygon error:nil]; XCTAssertNotNil(dictionary); PFPolygon *polygonFromDictionary = [PFPolygon polygonWithDictionary:dictionary]; diff --git a/Parse/Tests/Unit/PushCommandTests.m b/Parse/Tests/Unit/PushCommandTests.m index 3fe7f5a55..0d73d3822 100644 --- a/Parse/Tests/Unit/PushCommandTests.m +++ b/Parse/Tests/Unit/PushCommandTests.m @@ -24,7 +24,7 @@ @implementation PushCommandTests - (void)testEmptyPushCommand { PFMutablePushState *state = [[PFMutablePushState alloc] init]; - PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr"]; + PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"push"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -36,7 +36,7 @@ - (void)testPushCommandChannels { PFMutablePushState *state = [[PFMutablePushState alloc] init]; state.channels = [NSSet setWithObject:@"El Capitan!"]; - PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr"]; + PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"push"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -51,7 +51,7 @@ - (void)testPushCommandQuery { [queryState setEqualityConditionWithObject:@"value" forKey:@"key"]; state.queryState = queryState; - PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr"]; + PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"push"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -63,7 +63,7 @@ - (void)testPushCommandExpirationDate { PFMutablePushState *state = [[PFMutablePushState alloc] init]; state.expirationDate = [NSDate date]; - PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr"]; + PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"push"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -76,7 +76,7 @@ - (void)testPushCommandExpirationTimeInterval { PFMutablePushState *state = [[PFMutablePushState alloc] init]; state.expirationTimeInterval = @100500; - PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr"]; + PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"push"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -89,7 +89,7 @@ - (void)testPushCommandPushDate { PFMutablePushState *state = [[PFMutablePushState alloc] init]; state.pushDate = [NSDate dateWithTimeIntervalSinceNow:1.0]; - PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr"]; + PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"push"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -103,7 +103,7 @@ - (void)testPushCommandPayload { PFMutablePushState *state = [[PFMutablePushState alloc] init]; state.payload = @{ @"alert" : @"yolo" }; - PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr"]; + PFRESTPushCommand *command = [PFRESTPushCommand sendPushCommandWithPushState:state sessionToken:@"yarr" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"push"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); diff --git a/Parse/Tests/Unit/QueryCachedControllerTests.m b/Parse/Tests/Unit/QueryCachedControllerTests.m index a5439ae81..7dcc6d89b 100644 --- a/Parse/Tests/Unit/QueryCachedControllerTests.m +++ b/Parse/Tests/Unit/QueryCachedControllerTests.m @@ -369,7 +369,7 @@ - (void)testCacheKey { PFQueryState *state = [self sampleQueryStateWithCachePolicy:0]; PFCachedQueryController *controller = [PFCachedQueryController controllerWithCommonDataSource:dataSource]; - NSString *cacheKey = [PFRESTQueryCommand findCommandForQueryState:state withSessionToken:@"a"].cacheKey; + NSString *cacheKey = [PFRESTQueryCommand findCommandForQueryState:state withSessionToken:@"a" error:nil].cacheKey; XCTAssertEqualObjects([controller cacheKeyForQueryState:state sessionToken:@"a"], cacheKey); } @@ -380,7 +380,7 @@ - (void)testHasCachedResult { PFQueryState *state = [self sampleQueryStateWithCachePolicy:0]; PFCachedQueryController *controller = [PFCachedQueryController controllerWithCommonDataSource:dataSource]; - NSString *cacheKey = [PFRESTQueryCommand findCommandForQueryState:state withSessionToken:@"a"].cacheKey; + NSString *cacheKey = [PFRESTQueryCommand findCommandForQueryState:state withSessionToken:@"a" error:nil].cacheKey; NSString *jsonString = [PFJSONSerialization stringFromJSONObject:[self sampleCommandResult].result]; OCMStub([[cache ignoringNonObjectArgs] objectForKey:[OCMArg checkWithBlock:^BOOL(id obj) { @@ -389,7 +389,7 @@ - (void)testHasCachedResult { XCTAssertTrue([controller hasCachedResultForQueryState:state sessionToken:@"a"]); - cacheKey = [PFRESTQueryCommand findCommandForQueryState:state withSessionToken:nil].cacheKey; + cacheKey = [PFRESTQueryCommand findCommandForQueryState:state withSessionToken:nil error:nil].cacheKey; OCMStub([[cache ignoringNonObjectArgs] objectForKey:[OCMArg checkWithBlock:^BOOL(id obj) { return [obj isEqual:cacheKey]; }] maxAge:0]).andReturn(nil); diff --git a/Parse/Tests/Unit/QueryUnitTests.m b/Parse/Tests/Unit/QueryUnitTests.m index 25c060c85..a1da7d8c4 100644 --- a/Parse/Tests/Unit/QueryUnitTests.m +++ b/Parse/Tests/Unit/QueryUnitTests.m @@ -1413,4 +1413,23 @@ - (void)testHash { XCTAssertEqual([queryA hash], [queryB hash]); } +- (void)testReproduceIssue1202 { + [[Parse _currentManager] clearEventuallyQueue]; + [Parse _clearCurrentManager]; + [Parse enableLocalDatastore]; + [Parse setApplicationId:@"a" clientKey:@"b"]; + PFObject *objectA = [PFObject objectWithClassName:@"Object" dictionary:@{@"objectId":@"yolo", @"key": @"value"}]; + objectA.objectId = @"yolo"; + PFObject *objectB = [PFObject objectWithClassName:@"Object" dictionary:@{@"objectId":@"yolo", @"key": @"value"}]; + @try { + objectB.objectId = @"yolo"; + XCTFail(); + } + @catch (NSException *e) { + XCTAssertEqual(e.name, NSInternalInconsistencyException); + XCTAssertEqualObjects(e.reason, @"Attempted to change an objectId to one that's already known to the OfflineStore. className: Object old: (null), new: yolo"); + } +} + + @end diff --git a/Parse/Tests/Unit/RelationUnitTests.m b/Parse/Tests/Unit/RelationUnitTests.m index 69ca10822..b986b7a8d 100644 --- a/Parse/Tests/Unit/RelationUnitTests.m +++ b/Parse/Tests/Unit/RelationUnitTests.m @@ -141,7 +141,7 @@ - (void)testEncode { [relation _addKnownObject:mockedObject]; - NSDictionary *encoded = [relation encodeIntoDictionary]; + NSDictionary *encoded = [relation encodeIntoDictionary:nil]; XCTAssertEqual(1, [encoded[@"objects"] count]); XCTAssertNotNil(encoded); } diff --git a/Parse/Tests/Unit/URLSessionCommandRunnerTests.m b/Parse/Tests/Unit/URLSessionCommandRunnerTests.m index 41fa69b5b..01eae6f6d 100644 --- a/Parse/Tests/Unit/URLSessionCommandRunnerTests.m +++ b/Parse/Tests/Unit/URLSessionCommandRunnerTests.m @@ -17,6 +17,9 @@ #import "PFCommandURLRequestConstructor.h" #import "PFRESTCommand.h" #import "PFTestCase.h" +#import "PFObject.h" +#import "PFObjectPrivate.h" +#import "PFFieldOperation.h" #import "PFURLSession.h" #import "PFURLSessionCommandRunner_Private.h" @@ -64,7 +67,7 @@ - (void)testRunCommand { NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://foo.bar"]]; - OCMStub([mockedCommand resolveLocalIds]); + OCMStub([mockedCommand resolveLocalIds:(NSError * __autoreleasing *)[OCMArg anyPointer]]).andReturn(YES); OCMStub([mockedRequestConstructor getDataURLRequestAsyncForCommand:mockedCommand]).andReturn([BFTask taskWithResult:urlRequest]); [OCMExpect([mockedSession performDataURLRequestAsync:urlRequest @@ -135,7 +138,7 @@ - (void)testRunCommandRetry { __block int performDataURLRequestCount = 0; - OCMStub([mockedCommand resolveLocalIds]); + OCMStub([mockedCommand resolveLocalIds:(NSError * __autoreleasing *)[OCMArg anyPointer]]).andReturn(YES); OCMStub([mockedRequestConstructor getDataURLRequestAsyncForCommand:mockedCommand]).andReturn([BFTask taskWithResult:urlRequest]); [OCMStub([mockedSession performDataURLRequestAsync:urlRequest @@ -180,7 +183,7 @@ - (void)testRunCommandInvalidSession { code:kPFErrorInvalidSessionToken userInfo:nil]; - OCMStub([mockedCommand resolveLocalIds]); + OCMStub([mockedCommand resolveLocalIds:(NSError * __autoreleasing *)[OCMArg anyPointer]]); OCMStub([mockedRequestConstructor getDataURLRequestAsyncForCommand:mockedCommand]).andReturn([BFTask taskWithResult:urlRequest]); [OCMStub([mockedSession performDataURLRequestAsync:urlRequest @@ -228,7 +231,7 @@ - (void)testRunFileUpload { lastProgress = progress; } copy]; - OCMStub([mockedCommand resolveLocalIds]); + OCMStub([mockedCommand resolveLocalIds:(NSError * __autoreleasing *)[OCMArg anyPointer]]).andReturn(YES); OCMExpect([mockedRequestConstructor getFileUploadURLRequestAsyncForCommand:mockedCommand withContentType:@"content-type" @@ -275,7 +278,7 @@ - (void)testLocalIdResolution { NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://foo.bar"]]; - OCMExpect([mockedCommand resolveLocalIds]); + OCMExpect([mockedCommand resolveLocalIds:(NSError * __autoreleasing *)[OCMArg anyPointer]]).andReturn(YES); OCMStub([mockedRequestConstructor getDataURLRequestAsyncForCommand:mockedCommand]).andReturn([BFTask taskWithResult:urlRequest]); [OCMStub([mockedSession performDataURLRequestAsync:urlRequest @@ -301,4 +304,77 @@ - (void)testLocalIdResolution { OCMVerifyAll(mockedCommand); } +- (void)testLocalIdResolutionFailure { + PFObject *object = [PFObject objectWithoutDataWithClassName:@"Yolo" localId:@"localId"]; + id command = [PFRESTCommand commandWithHTTPPath:@"" httpMethod:@"" parameters:@{@"object": object} sessionToken:nil error:nil]; + NSError *error; + [command resolveLocalIds:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqualObjects(error.localizedDescription, @"Tried to save an object with a pointer to a new, unsaved object. (Yolo)"); +} + +- (void)testLocalIdResolutionFailureWithNoLocalId { + PFObject *object = [PFObject objectWithClassName:@"Yolo"]; + id command = [PFRESTCommand commandWithHTTPPath:@"" httpMethod:@"" parameters:@{@"object": object} sessionToken:nil error:nil]; + NSError *error; + [command resolveLocalIds:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqualObjects(error.localizedDescription, @"Tried to resolve a localId for an object with no localId. (Yolo)"); +} + +- (void)testLocalIdResolutionWithArray { + PFObject *object = [PFObject objectWithClassName:@"Yolo"]; + id command = [PFRESTCommand commandWithHTTPPath:@"" httpMethod:@"" parameters:@{@"values":@[@(1), object]} sessionToken:nil error:nil]; + NSError *error; + [command resolveLocalIds:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqualObjects(error.localizedDescription, @"Tried to resolve a localId for an object with no localId. (Yolo)"); +} + +- (void)testLocalIdResolutionWithArrayAndMutlipleErrors { + PFObject *objectWithLocalId = [PFObject objectWithoutDataWithClassName:@"Yolo" localId:@"localId"]; + PFObject *object = [PFObject objectWithClassName:@"Yolo"]; + id command = [PFRESTCommand commandWithHTTPPath:@"" httpMethod:@"" parameters:@{@"values":@[objectWithLocalId, object]} sessionToken:nil error:nil]; + NSError *error; + [command resolveLocalIds:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertEqualObjects(error.localizedDescription, @"Tried to save an object with a pointer to a new, unsaved object. (Yolo)"); +} + +- (void)testLocalIdResolutionWithOperations { + NSArray *possibleErrors = @[@"Tried to save an object with a pointer to a new, unsaved object. (Yolo)", + @"Tried to resolve a localId for an object with no localId. (Yolo)"]; + NSError *error; + PFObject *objectWithLocalId = [PFObject objectWithoutDataWithClassName:@"Yolo" localId:@"localId"]; + PFObject *object = [PFObject objectWithClassName:@"Yolo"]; + PFAddOperation *addOperation = [PFAddOperation addWithObjects:@[objectWithLocalId, object]]; + id command = [PFRESTCommand commandWithHTTPPath:@"" httpMethod:@"" parameters:@{@"values":addOperation} sessionToken:nil error:nil]; + [command resolveLocalIds:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertTrue([possibleErrors indexOfObject:error.localizedDescription] != NSNotFound); + + error = nil; + + PFAddUniqueOperation *addUniqueOperation = [PFAddUniqueOperation addUniqueWithObjects:@[objectWithLocalId, object]]; + command = [PFRESTCommand commandWithHTTPPath:@"" httpMethod:@"" parameters:@{@"values":addUniqueOperation} sessionToken:nil error:nil]; + [command resolveLocalIds:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertTrue([possibleErrors indexOfObject:error.localizedDescription] != NSNotFound); + + error = nil; + + PFRemoveOperation *removeOperation = [PFRemoveOperation removeWithObjects:@[objectWithLocalId, object]]; + command = [PFRESTCommand commandWithHTTPPath:@"" httpMethod:@"" parameters:@{@"values":removeOperation} sessionToken:nil error:nil]; + [command resolveLocalIds:&error]; + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, PFParseErrorDomain); + XCTAssertTrue([possibleErrors indexOfObject:error.localizedDescription] != NSNotFound); +} + @end diff --git a/Parse/Tests/Unit/UserCommandTests.m b/Parse/Tests/Unit/UserCommandTests.m index 148b00c1f..688b0e83a 100644 --- a/Parse/Tests/Unit/UserCommandTests.m +++ b/Parse/Tests/Unit/UserCommandTests.m @@ -20,7 +20,8 @@ @implementation UserCommandTests - (void)testLogInCommand { PFRESTUserCommand *command = [PFRESTUserCommand logInUserCommandWithUsername:@"a" password:@"b" - revocableSession:YES]; + revocableSession:YES + error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"login"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodGET); @@ -33,7 +34,8 @@ - (void)testLogInCommand { command = [PFRESTUserCommand logInUserCommandWithUsername:@"a" password:@"b" - revocableSession:NO]; + revocableSession:NO + error:nil]; XCTAssertNotNil(command); XCTAssertEqual(command.additionalRequestHeaders.count, 0); XCTAssertFalse(command.revocableSessionEnabled); @@ -42,7 +44,8 @@ - (void)testLogInCommand { - (void)testServiceLoginCommandWithAuthTypeData { PFRESTUserCommand *command = [PFRESTUserCommand serviceLoginUserCommandWithAuthenticationType:@"a" authenticationData:@{ @"b" : @"c" } - revocableSession:YES]; + revocableSession:YES + error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"users"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -55,7 +58,8 @@ - (void)testServiceLoginCommandWithAuthTypeData { command = [PFRESTUserCommand serviceLoginUserCommandWithAuthenticationType:@"a" authenticationData:@{ @"b" : @"c" } - revocableSession:NO]; + revocableSession:NO + error:nil]; XCTAssertNotNil(command); XCTAssertEqual(command.additionalRequestHeaders.count, 0); XCTAssertFalse(command.revocableSessionEnabled); @@ -64,7 +68,8 @@ - (void)testServiceLoginCommandWithAuthTypeData { - (void)testServiceLoginCommandWithParameters { PFRESTUserCommand *command = [PFRESTUserCommand serviceLoginUserCommandWithParameters:@{ @"authData" : @{@"b" : @"c"} } revocableSession:YES - sessionToken:@"Yarr"]; + sessionToken:@"Yarr" + error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"users"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -77,7 +82,8 @@ - (void)testServiceLoginCommandWithParameters { command = [PFRESTUserCommand serviceLoginUserCommandWithParameters:@{ @"authData" : @{@"b" : @"c"} } revocableSession:NO - sessionToken:@"Yarr!"]; + sessionToken:@"Yarr!" + error:nil]; XCTAssertNotNil(command); XCTAssertEqual(command.additionalRequestHeaders.count, 0); XCTAssertFalse(command.revocableSessionEnabled); @@ -86,7 +92,8 @@ - (void)testServiceLoginCommandWithParameters { - (void)testSignUpCommand { PFRESTUserCommand *command = [PFRESTUserCommand signUpUserCommandWithParameters:@{ @"k" : @"v" } revocableSession:YES - sessionToken:@"Boom"]; + sessionToken:@"Boom" + error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"users"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -97,14 +104,15 @@ - (void)testSignUpCommand { command = [PFRESTUserCommand signUpUserCommandWithParameters:@{ @"k" : @"v" } revocableSession:NO - sessionToken:@"Boom"]; + sessionToken:@"Boom" + error:nil]; XCTAssertNotNil(command); XCTAssertEqual(command.additionalRequestHeaders.count, 0); XCTAssertFalse(command.revocableSessionEnabled); } - (void)testGetCurrentUserCommand { - PFRESTUserCommand *command = [PFRESTUserCommand getCurrentUserCommandWithSessionToken:@"yolo"]; + PFRESTUserCommand *command = [PFRESTUserCommand getCurrentUserCommandWithSessionToken:@"yolo" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"users/me"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodGET); @@ -113,7 +121,7 @@ - (void)testGetCurrentUserCommand { } - (void)testUpgradeToRevocableSessionCommand { - PFRESTUserCommand *command = [PFRESTUserCommand upgradeToRevocableSessionCommandWithSessionToken:@"yolo"]; + PFRESTUserCommand *command = [PFRESTUserCommand upgradeToRevocableSessionCommandWithSessionToken:@"yolo" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"upgradeToRevocableSession"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -122,7 +130,7 @@ - (void)testUpgradeToRevocableSessionCommand { } - (void)testLogOutUserCommand { - PFRESTUserCommand *command = [PFRESTUserCommand logOutUserCommandWithSessionToken:@"yolo"]; + PFRESTUserCommand *command = [PFRESTUserCommand logOutUserCommandWithSessionToken:@"yolo" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"logout"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); @@ -131,7 +139,7 @@ - (void)testLogOutUserCommand { } - (void)testResetPasswordCommand { - PFRESTUserCommand *command = [PFRESTUserCommand resetPasswordCommandForUserWithEmail:@"nlutsenko@me.com"]; + PFRESTUserCommand *command = [PFRESTUserCommand resetPasswordCommandForUserWithEmail:@"nlutsenko@me.com" error:nil]; XCTAssertNotNil(command); XCTAssertEqualObjects(command.httpPath, @"requestPasswordReset"); XCTAssertEqualObjects(command.httpMethod, PFHTTPRequestMethodPOST); diff --git a/ParseFacebookUtils/Resources/Info-iOS.plist b/ParseFacebookUtils/Resources/Info-iOS.plist index 33c68a5c2..a347edc0c 100644 --- a/ParseFacebookUtils/Resources/Info-iOS.plist +++ b/ParseFacebookUtils/Resources/Info-iOS.plist @@ -13,7 +13,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -22,7 +22,7 @@ iPhoneOS CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 MinimumOSVersion 6.0 diff --git a/ParseFacebookUtils/Resources/Info-tvOS.plist b/ParseFacebookUtils/Resources/Info-tvOS.plist index 9527a11c0..c7f81e51f 100644 --- a/ParseFacebookUtils/Resources/Info-tvOS.plist +++ b/ParseFacebookUtils/Resources/Info-tvOS.plist @@ -13,10 +13,10 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 diff --git a/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/Resources/Info.plist b/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/Resources/Info.plist index 55c8c07e3..d2050105a 100644 --- a/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/Resources/Info.plist +++ b/ParseStarterProject/OSX/ParseOSXStarterProject-Swift/Resources/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSMainNibFile diff --git a/ParseStarterProject/OSX/ParseOSXStarterProject/Resources/Info.plist b/ParseStarterProject/OSX/ParseOSXStarterProject/Resources/Info.plist index bdb3a1e4b..dd11b64fb 100644 --- a/ParseStarterProject/OSX/ParseOSXStarterProject/Resources/Info.plist +++ b/ParseStarterProject/OSX/ParseOSXStarterProject/Resources/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSMainNibFile diff --git a/ParseStarterProject/iOS/ParseStarterProject-Swift/Resources/Info.plist b/ParseStarterProject/iOS/ParseStarterProject-Swift/Resources/Info.plist index ecf973294..a63104ac0 100644 --- a/ParseStarterProject/iOS/ParseStarterProject-Swift/Resources/Info.plist +++ b/ParseStarterProject/iOS/ParseStarterProject-Swift/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/ParseStarterProject/iOS/ParseStarterProject/Resources/Info.plist b/ParseStarterProject/iOS/ParseStarterProject/Resources/Info.plist index f7f03cd45..cae410c53 100644 --- a/ParseStarterProject/iOS/ParseStarterProject/Resources/Info.plist +++ b/ParseStarterProject/iOS/ParseStarterProject/Resources/Info.plist @@ -19,11 +19,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 LSRequiresIPhoneOS NSMainNibFile diff --git a/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter/Info.plist b/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter/Info.plist index c1cb3c95f..fc4b6acba 100644 --- a/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter/Info.plist +++ b/ParseStarterProject/tvOS/ParseStarterProject-Swift/ParseStarter/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 LSRequiresIPhoneOS UIMainStoryboardFile diff --git a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter Extension/Info.plist b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter Extension/Info.plist index 667c67427..c7a912c4f 100644 --- a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter Extension/Info.plist +++ b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter Extension/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 NSExtension NSExtensionAttributes diff --git a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter/Info.plist b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter/Info.plist index 51542af38..4eb2c5267 100644 --- a/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter/Info.plist +++ b/ParseStarterProject/watchOS/ParseStarterProject-Swift/ParseStarter/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/ParseStarterProject/watchOS/ParseStarterProject-Swift/Resources/Info.plist b/ParseStarterProject/watchOS/ParseStarterProject-Swift/Resources/Info.plist index 753250ca8..8886132b6 100644 --- a/ParseStarterProject/watchOS/ParseStarterProject-Swift/Resources/Info.plist +++ b/ParseStarterProject/watchOS/ParseStarterProject-Swift/Resources/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 LSRequiresIPhoneOS UIMainStoryboardFile diff --git a/ParseTwitterUtils/Resources/Info.plist b/ParseTwitterUtils/Resources/Info.plist index c6594d610..8d3078dce 100644 --- a/ParseTwitterUtils/Resources/Info.plist +++ b/ParseTwitterUtils/Resources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.16.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -22,7 +22,7 @@ iPhoneOS CFBundleVersion - 1.16.0 + 1.17.0-alpha.5 MinimumOSVersion 6.0 diff --git a/ParseUI/Resources/Info.plist b/ParseUI/Resources/Info.plist index 7cead4508..f8c274285 100644 --- a/ParseUI/Resources/Info.plist +++ b/ParseUI/Resources/Info.plist @@ -13,7 +13,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.2.0 + 1.17.0-alpha.5 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -22,7 +22,7 @@ iPhoneOS CFBundleVersion - 1.2.0 + 1.17.0-alpha.5 MinimumOSVersion 7.0 diff --git a/Rakefile b/Rakefile index c2ff32da7..daf5147aa 100644 --- a/Rakefile +++ b/Rakefile @@ -32,6 +32,7 @@ module Constants File.join(script_folder, 'ParseFacebookUtils', 'Resources', 'Info-iOS.plist'), File.join(script_folder, 'ParseFacebookUtils', 'Resources', 'Info-tvOS.plist'), File.join(script_folder, 'ParseTwitterUtils', 'Resources', 'Info.plist'), + File.join(script_folder, 'ParseUI', 'Resources', 'Info.plist'), File.join(script_folder, 'ParseStarterProject', 'iOS', 'ParseStarterProject', 'Resources', 'Info.plist'), File.join(script_folder, 'ParseStarterProject', 'iOS', 'ParseStarterProject-Swift', 'Resources', 'Info.plist'), File.join(script_folder, 'ParseStarterProject', 'OSX', 'ParseOSXStarterProject', 'Resources', 'Info.plist'), @@ -301,6 +302,12 @@ namespace :package do Constants.update_version(version) end + desc 'Build all frameworks and starters' + task :release do |_| + Rake::Task['package:frameworks'].invoke + Rake::Task['package:starters'].invoke + end + desc 'Build and package all frameworks for the release' task :frameworks, [:version] => :prepare do |_, args| version = args[:version] || Constants.current_version @@ -623,12 +630,6 @@ namespace :test do end end - desc 'Run Deployment Tests' - task :deployment do |_| - Rake::Task['package:frameworks'].invoke - Rake::Task['package:starters'].invoke - end - desc 'Run Starter Project Tests' task :starters do |_| results = [] diff --git a/Scripts/publish.sh b/Scripts/publish.sh index 55d408f7e..6dcf77e57 100755 --- a/Scripts/publish.sh +++ b/Scripts/publish.sh @@ -1,4 +1,4 @@ #!/bin/sh -e gem install bundler bundle install -bundle exec pod trunk push Parse.podspec +bundle exec pod trunk push Parse.podspec --verbose