RLMError.mm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2022 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMError_Private.hpp"
  19. #import "RLMUtil.hpp"
  20. #import "RLMSyncSession_Private.hpp"
  21. #import <realm/object-store/sync/app.hpp>
  22. #import <realm/util/basic_system_errors.hpp>
  23. #import <realm/sync/client.hpp>
  24. // NEXT-MAJOR: we should merge these all into a single error domain/error enum
  25. NSString *const RLMErrorDomain = @"io.realm";
  26. NSString *const RLMUnknownSystemErrorDomain = @"io.realm.unknown";
  27. NSString *const RLMSyncErrorDomain = @"io.realm.sync";
  28. NSString *const RLMSyncAuthErrorDomain = @"io.realm.sync.auth";
  29. NSString *const RLMAppErrorDomain = @"io.realm.app";
  30. NSString *const kRLMSyncPathOfRealmBackupCopyKey = @"recovered_realm_location_path";
  31. NSString *const kRLMSyncErrorActionTokenKey = @"error_action_token";
  32. NSString *const RLMErrorCodeKey = @"Error Code";
  33. NSString *const RLMErrorCodeNameKey = @"Error Name";
  34. NSString *const RLMServerLogURLKey = @"Server Log URL";
  35. NSString *const RLMCompensatingWriteInfoKey = @"Compensating Write Info";
  36. NSString *const RLMHTTPStatusCodeKey = @"HTTP Status Code";
  37. static NSString *const RLMDeprecatedErrorCodeKey = @"Error Code";
  38. namespace {
  39. NSInteger translateFileError(realm::ErrorCodes::Error code) {
  40. using ec = realm::ErrorCodes::Error;
  41. switch (code) {
  42. // Local errors
  43. case ec::AddressSpaceExhausted: return RLMErrorAddressSpaceExhausted;
  44. case ec::DeleteOnOpenRealm: return RLMErrorAlreadyOpen;
  45. case ec::FileAlreadyExists: return RLMErrorFileExists;
  46. case ec::FileFormatUpgradeRequired: return RLMErrorFileFormatUpgradeRequired;
  47. case ec::FileNotFound: return RLMErrorFileNotFound;
  48. case ec::FileOperationFailed: return RLMErrorFileOperationFailed;
  49. case ec::IncompatibleHistories: return RLMErrorIncompatibleHistories;
  50. case ec::IncompatibleLockFile: return RLMErrorIncompatibleLockFile;
  51. case ec::IncompatibleSession: return RLMErrorIncompatibleSession;
  52. case ec::InvalidDatabase: return RLMErrorInvalidDatabase;
  53. case ec::MultipleSyncAgents: return RLMErrorMultipleSyncAgents;
  54. case ec::NoSubscriptionForWrite: return RLMErrorNoSubscriptionForWrite;
  55. case ec::OutOfDiskSpace: return RLMErrorOutOfDiskSpace;
  56. case ec::PermissionDenied: return RLMErrorFilePermissionDenied;
  57. case ec::SchemaMismatch: return RLMErrorSchemaMismatch;
  58. case ec::SubscriptionFailed: return RLMErrorSubscriptionFailed;
  59. case ec::UnsupportedFileFormatVersion: return RLMErrorUnsupportedFileFormatVersion;
  60. // Sync errors
  61. case ec::AuthError: return RLMSyncErrorClientUserError;
  62. case ec::SyncPermissionDenied: return RLMSyncErrorPermissionDeniedError;
  63. case ec::SyncCompensatingWrite: return RLMSyncErrorWriteRejected;
  64. case ec::SyncConnectFailed: return RLMSyncErrorConnectionFailed;
  65. case ec::TlsHandshakeFailed: return RLMSyncErrorTLSHandshakeFailed;
  66. case ec::SyncConnectTimeout: return ETIMEDOUT;
  67. // App errors
  68. case ec::APIKeyAlreadyExists: return RLMAppErrorAPIKeyAlreadyExists;
  69. case ec::AccountNameInUse: return RLMAppErrorAccountNameInUse;
  70. case ec::AppUnknownError: return RLMAppErrorUnknown;
  71. case ec::AuthProviderNotFound: return RLMAppErrorAuthProviderNotFound;
  72. case ec::DomainNotAllowed: return RLMAppErrorDomainNotAllowed;
  73. case ec::ExecutionTimeLimitExceeded: return RLMAppErrorExecutionTimeLimitExceeded;
  74. case ec::FunctionExecutionError: return RLMAppErrorFunctionExecutionError;
  75. case ec::FunctionInvalid: return RLMAppErrorFunctionInvalid;
  76. case ec::FunctionNotFound: return RLMAppErrorFunctionNotFound;
  77. case ec::FunctionSyntaxError: return RLMAppErrorFunctionSyntaxError;
  78. case ec::InvalidPassword: return RLMAppErrorInvalidPassword;
  79. case ec::InvalidSession: return RLMAppErrorInvalidSession;
  80. case ec::MaintenanceInProgress: return RLMAppErrorMaintenanceInProgress;
  81. case ec::MissingParameter: return RLMAppErrorMissingParameter;
  82. case ec::MongoDBError: return RLMAppErrorMongoDBError;
  83. case ec::NotCallable: return RLMAppErrorNotCallable;
  84. case ec::ReadSizeLimitExceeded: return RLMAppErrorReadSizeLimitExceeded;
  85. case ec::UserAlreadyConfirmed: return RLMAppErrorUserAlreadyConfirmed;
  86. case ec::UserAppDomainMismatch: return RLMAppErrorUserAppDomainMismatch;
  87. case ec::UserDisabled: return RLMAppErrorUserDisabled;
  88. case ec::UserNotFound: return RLMAppErrorUserNotFound;
  89. case ec::ValueAlreadyExists: return RLMAppErrorValueAlreadyExists;
  90. case ec::ValueDuplicateName: return RLMAppErrorValueDuplicateName;
  91. case ec::ValueNotFound: return RLMAppErrorValueNotFound;
  92. case ec::AWSError:
  93. case ec::GCMError:
  94. case ec::HTTPError:
  95. case ec::InternalServerError:
  96. case ec::TwilioError:
  97. return RLMAppErrorInternalServerError;
  98. case ec::ArgumentsNotAllowed:
  99. case ec::BadRequest:
  100. case ec::InvalidParameter:
  101. return RLMAppErrorBadRequest;
  102. default: {
  103. auto category = realm::ErrorCodes::error_categories(code);
  104. if (category.test(realm::ErrorCategory::file_access)) {
  105. return RLMErrorFileAccess;
  106. }
  107. if (category.test(realm::ErrorCategory::app_error)) {
  108. return RLMAppErrorUnknown;
  109. }
  110. if (category.test(realm::ErrorCategory::sync_error)) {
  111. return RLMSyncErrorClientInternalError;
  112. }
  113. return RLMErrorFail;
  114. }
  115. }
  116. }
  117. NSString *errorDomain(realm::ErrorCodes::Error error) {
  118. if (error == realm::ErrorCodes::SyncConnectTimeout) {
  119. return NSPOSIXErrorDomain;
  120. }
  121. auto category = realm::ErrorCodes::error_categories(error);
  122. if (category.test(realm::ErrorCategory::sync_error)) {
  123. return RLMSyncErrorDomain;
  124. }
  125. if (category.test(realm::ErrorCategory::app_error)) {
  126. return RLMAppErrorDomain;
  127. }
  128. return RLMErrorDomain;
  129. }
  130. NSString *errorString(realm::ErrorCodes::Error error) {
  131. return RLMStringViewToNSString(realm::ErrorCodes::error_string(error));
  132. }
  133. NSError *translateSystemError(std::error_code ec, const char *msg) {
  134. int code = ec.value();
  135. BOOL isGenericCategoryError = ec.category() == std::generic_category()
  136. || ec.category() == realm::util::error::basic_system_error_category();
  137. NSString *errorDomain = isGenericCategoryError ? NSPOSIXErrorDomain : RLMUnknownSystemErrorDomain;
  138. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  139. userInfo[NSLocalizedDescriptionKey] = @(msg);
  140. // FIXME: remove these in v11
  141. userInfo[@"Error Code"] = @(code);
  142. userInfo[@"Category"] = @(ec.category().name());
  143. return [NSError errorWithDomain:errorDomain code:code userInfo:userInfo.copy];
  144. }
  145. } // anonymous namespace
  146. NSError *makeError(realm::Status const& status) {
  147. if (status.is_ok()) {
  148. return nil;
  149. }
  150. auto code = translateFileError(status.code());
  151. return [NSError errorWithDomain:errorDomain(status.code())
  152. code:code
  153. userInfo:@{NSLocalizedDescriptionKey: @(status.reason().c_str()),
  154. RLMDeprecatedErrorCodeKey: @(code),
  155. RLMErrorCodeNameKey: errorString(status.code())}];
  156. }
  157. NSError *makeError(realm::Exception const& exception) {
  158. NSInteger code = translateFileError(exception.code());
  159. return [NSError errorWithDomain:errorDomain(exception.code())
  160. code:code
  161. userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
  162. RLMDeprecatedErrorCodeKey: @(code),
  163. RLMErrorCodeNameKey: errorString(exception.code())}];
  164. }
  165. NSError *makeError(realm::FileAccessError const& exception) {
  166. NSInteger code = translateFileError(exception.code());
  167. return [NSError errorWithDomain:errorDomain(exception.code())
  168. code:code
  169. userInfo:@{NSLocalizedDescriptionKey: @(exception.what()),
  170. NSFilePathErrorKey: @(exception.get_path().data()),
  171. RLMDeprecatedErrorCodeKey: @(code),
  172. RLMErrorCodeNameKey: errorString(exception.code())}];
  173. }
  174. NSError *makeError(std::exception const& exception) {
  175. return [NSError errorWithDomain:RLMErrorDomain
  176. code:RLMErrorFail
  177. userInfo:@{NSLocalizedDescriptionKey: @(exception.what())}];
  178. }
  179. NSError *makeError(std::system_error const& exception) {
  180. return translateSystemError(exception.code(), exception.what());
  181. }
  182. __attribute__((objc_direct_members))
  183. @implementation RLMCompensatingWriteInfo {
  184. realm::sync::CompensatingWriteErrorInfo _info;
  185. }
  186. - (instancetype)initWithInfo:(realm::sync::CompensatingWriteErrorInfo&&)info {
  187. if ((self = [super init])) {
  188. _info = std::move(info);
  189. }
  190. return self;
  191. }
  192. - (NSString *)objectType {
  193. return @(_info.object_name.c_str());
  194. }
  195. - (NSString *)reason {
  196. return @(_info.reason.c_str());
  197. }
  198. - (id<RLMValue>)primaryKey {
  199. return RLMMixedToObjc(_info.primary_key);
  200. }
  201. @end
  202. NSError *makeError(realm::SyncError&& error) {
  203. auto& status = error.status;
  204. if (status.is_ok()) {
  205. return nil;
  206. }
  207. NSMutableDictionary *userInfo = [NSMutableDictionary new];
  208. userInfo[NSLocalizedDescriptionKey] = RLMStringViewToNSString(error.simple_message);
  209. if (!error.logURL.empty()) {
  210. userInfo[RLMServerLogURLKey] = RLMStringViewToNSString(error.logURL);
  211. }
  212. if (!error.compensating_writes_info.empty()) {
  213. NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:error.compensating_writes_info.size()];
  214. for (auto& info : error.compensating_writes_info) {
  215. [array addObject:[[RLMCompensatingWriteInfo alloc] initWithInfo:std::move(info)]];
  216. }
  217. userInfo[RLMCompensatingWriteInfoKey] = [array copy];
  218. }
  219. for (auto& pair : error.user_info) {
  220. if (pair.first == realm::SyncError::c_original_file_path_key) {
  221. userInfo[kRLMSyncErrorActionTokenKey] =
  222. [[RLMSyncErrorActionToken alloc] initWithOriginalPath:pair.second];
  223. }
  224. else if (pair.first == realm::SyncError::c_recovery_file_path_key) {
  225. userInfo[kRLMSyncPathOfRealmBackupCopyKey] = @(pair.second.c_str());
  226. }
  227. }
  228. int errorCode = RLMSyncErrorClientInternalError;
  229. NSString *errorDomain = RLMSyncErrorDomain;
  230. using enum realm::ErrorCodes::Error;
  231. auto code = error.status.code();
  232. bool isSyncError = realm::ErrorCodes::error_categories(code).test(realm::ErrorCategory::sync_error);
  233. switch (code) {
  234. case SyncPermissionDenied:
  235. errorCode = RLMSyncErrorPermissionDeniedError;
  236. break;
  237. case AuthError:
  238. errorCode = RLMSyncErrorClientUserError;
  239. break;
  240. case SyncCompensatingWrite:
  241. errorCode = RLMSyncErrorWriteRejected;
  242. break;
  243. case SyncConnectFailed:
  244. errorCode = RLMSyncErrorConnectionFailed;
  245. break;
  246. case SyncConnectTimeout:
  247. errorCode = ETIMEDOUT;
  248. errorDomain = NSPOSIXErrorDomain;
  249. break;
  250. default:
  251. if (error.is_client_reset_requested())
  252. errorCode = RLMSyncErrorClientResetError;
  253. else if (isSyncError)
  254. errorCode = RLMSyncErrorClientSessionError;
  255. else if (!error.is_fatal)
  256. return nil;
  257. break;
  258. }
  259. return [NSError errorWithDomain:errorDomain code:errorCode userInfo:userInfo.copy];
  260. }
  261. NSError *makeError(realm::app::AppError const& appError) {
  262. auto& status = appError.to_status();
  263. if (status.is_ok()) {
  264. return nil;
  265. }
  266. // Core uses the same error code for both sync and app auth errors, but we
  267. // have separate ones
  268. auto code = translateFileError(status.code());
  269. auto domain = errorDomain(status.code());
  270. if (domain == RLMSyncErrorDomain && code == RLMSyncErrorClientUserError) {
  271. domain = RLMAppErrorDomain;
  272. code = RLMAppErrorAuthError;
  273. }
  274. return [NSError errorWithDomain:domain code:code
  275. userInfo:@{NSLocalizedDescriptionKey: @(status.reason().c_str()),
  276. RLMDeprecatedErrorCodeKey: @(code),
  277. RLMErrorCodeNameKey: errorString(status.code()),
  278. RLMServerLogURLKey: @(appError.link_to_server_logs.c_str())}];
  279. }