diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m index acacc3a34c4638..bbbe194c5e9867 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m @@ -42,6 +42,25 @@ - (void)testEncodingString XCTAssertEqualObjects(json, RCTJSONStringify(text, NULL)); } +- (void)testEncodingNSError +{ + NSError *underlyingError = [NSError errorWithDomain:@"underlyingDomain" code:421 userInfo:nil]; + NSError *err = [NSError errorWithDomain:@"domain" code:68 userInfo:@{@"NSUnderlyingError": underlyingError}]; + + // An assertion on the full object would be too brittle since it contains an iOS stack trace + // so we are relying on the behavior of RCTJSONParse, which is tested below. + NSDictionary *jsonObject = RCTJSErrorFromNSError(err); + NSString *jsonString = RCTJSONStringify(jsonObject, NULL); + NSDictionary *json = RCTJSONParse(jsonString, NULL); + XCTAssertEqualObjects(json[@"code"], @"EDOMAIN68"); + XCTAssertEqualObjects(json[@"message"], @"The operation couldn’t be completed. (domain error 68.)"); + XCTAssertEqualObjects(json[@"domain"], @"domain"); + XCTAssertEqualObjects(json[@"userInfo"][@"NSUnderlyingError"][@"code"], @"421"); + XCTAssertEqualObjects(json[@"userInfo"][@"NSUnderlyingError"][@"message"], @"underlying error"); + XCTAssertEqualObjects(json[@"userInfo"][@"NSUnderlyingError"][@"domain"], @"underlyingDomain"); +} + + - (void)testDecodingObject { NSDictionary *obj = @{@"foo": @"bar"}; diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index ff50a647994acd..264ae95a0077f5 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -413,18 +413,28 @@ BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector) { NSString *errorMessage; NSArray *stackTrace = [NSThread callStackSymbols]; + NSMutableDictionary *userInfo; NSMutableDictionary *errorInfo = [NSMutableDictionary dictionaryWithObject:stackTrace forKey:@"nativeStackIOS"]; if (error) { errorMessage = error.localizedDescription ?: @"Unknown error from a native module"; errorInfo[@"domain"] = error.domain ?: RCTErrorDomain; + if (error.userInfo) { + userInfo = [error.userInfo mutableCopy]; + if (userInfo != nil && userInfo[NSUnderlyingErrorKey] != nil) { + NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey]; + NSString *underlyingCode = [NSString stringWithFormat:@"%d", (int)underlyingError.code]; + userInfo[NSUnderlyingErrorKey] = RCTJSErrorFromCodeMessageAndNSError(underlyingCode, @"underlying error", underlyingError); + } + } } else { errorMessage = @"Unknown error from a native module"; errorInfo[@"domain"] = RCTErrorDomain; + userInfo = nil; } errorInfo[@"code"] = code ?: RCTErrorUnspecified; - errorInfo[@"userInfo"] = RCTNullIfNil(error.userInfo); + errorInfo[@"userInfo"] = RCTNullIfNil(userInfo); // Allow for explicit overriding of the error message errorMessage = message ?: errorMessage;