From c144bbfb7ebdd174707638eaa344a9fddb38f253 Mon Sep 17 00:00:00 2001 From: Neil Sarkar Date: Wed, 26 Oct 2016 01:40:24 -0700 Subject: [PATCH] Allow serializing underlying NSError objects, closes #10506 Summary: Explain the **motivation** for making this change. What existing problem does the pull request solve? See https://github.com/facebook/react-native/issues/10506. A native `NSError` with `NSUnderlyingErrorKey` set causes a JSON stringify error from the websocket dispatcher if remote debugging is enabled. **Test plan (required)** I'm not familiar with the react native testing framework. Happy to add a test for this if someone can point me to where this part of the codebase is exercised :) I did some spot checks with nil user dictionaries and nil underlying errors here. The case that this solves is testable using https://github.com/superseriouscompany/react-native-error-repro, specifically: ```objective-c NSError *underlyingError = [NSError errorWithDomain:@"underlyingDomain" code:421 userInfo:nil]; NSError *err = [NSError errorWithDomain:@"domain" code:68 userInfo:@{@"NSUnderlyingError": underlyingError}]; reject(@"foo", @"bar", err); ``` Closes https://github.com/facebook/react-native/pull/10507 Differential Revision: D4080802 Pulled By: lacker fbshipit-source-id: 93a41d9e9a710e406a6ccac214a5617271b4bede --- .../UIExplorerUnitTests/RCTJSONTests.m | 19 +++++++++++++++++++ React/Base/RCTUtils.m | 12 +++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m index acacc3a34..bbbe194c5 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTJSONTests.m @@ -42,6 +42,25 @@ 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 ff50a6479..264ae95a0 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -413,18 +413,28 @@ NSDictionary *RCTJSErrorFromCodeMessageAndNSError(NSString *code { 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;