* Added MYReturnError.
* Better iPhone support in ExceptionUtils.
* Make sure "All Tests Passed/Failed" message is always logged.
5 // Created by Jens Alfke on 2/25/09.
6 // Copyright 2009 Jens Alfke. All rights reserved.
9 #import "MYErrorUtils.h"
11 #import "CollectionUtils.h"
12 #import <Foundation/Foundation.h>
13 #import <Security/SecBase.h>
16 NSString* const MYErrorDomain = @"MYErrorDomain";
19 static NSError *MYMakeErrorV( int errorCode, NSString *domain, NSString *message, va_list args )
21 message = [[NSString alloc] initWithFormat: message arguments: args];
22 Log(@"MYError #%i: %@",errorCode,message);
23 NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
24 message, NSLocalizedDescriptionKey,
27 return [NSError errorWithDomain: domain
33 NSError *MYError( int errorCode, NSString *domain, NSString *message, ... )
36 va_start(args,message);
37 NSError *error = MYMakeErrorV(errorCode,domain,message,args);
43 BOOL MYReturnError( NSError **outError,
44 int errorCode, NSString *domain, NSString *messageFormat, ... )
49 va_start(args,messageFormat);
50 *outError = MYMakeErrorV(errorCode, domain, messageFormat, args);
59 BOOL MYMiscError( NSError **error, NSString *message, ... )
63 va_start(args,message);
64 *error = MYMakeErrorV(kMYErrorMisc,MYErrorDomain, message,args);
71 static NSString* printableOSType( OSType t ) {
72 if (t < 0x20202020 || t > 0x7e7e7e7e)
78 buf.ostype = CFSwapInt32HostToBig(t);
79 for (int i=0; i<4; i++)
80 if (buf.ch[i] < 0x20 || buf.ch[i] > 0x7E)
82 return [[[NSString alloc] initWithBytes: &buf.ch length: 4 encoding: NSMacOSRomanStringEncoding]
87 static NSString* printableErrorCode( NSInteger code ) {
89 return $sprintf(@"%u", code); // CSSM errors are huge unsigned values > 0x80000000
90 NSString *result = printableOSType((OSType)code);
92 return result; // CoreAudio errors are OSTypes (4-char strings)
93 return $sprintf(@"%i", code); // Default: OSStatus and errno values are signed
96 static NSString* MYShortErrorDomainName( NSString *domain ) {
97 if ([domain hasPrefix: @"kCFErrorDomain"])
98 domain = [domain substringFromIndex: 14];
100 if ([domain hasSuffix: @"ErrorDomain"])
101 domain = [domain substringToIndex: domain.length - 11];
102 if ([domain hasPrefix: @"NS"])
103 domain = [domain substringFromIndex: 2];
108 NSString* MYErrorName( NSString *domain, NSInteger code ) {
111 NSString *codeStr = printableErrorCode(code);
114 NSString *result = nil;
116 if ($equal(domain,NSPOSIXErrorDomain)) {
117 // Interpret POSIX errors via strerror
118 // (which unfortunately returns a description, not the name of the constant)
119 const char *name = strerror(code);
121 result = [NSString stringWithCString: name encoding: NSASCIIStringEncoding];
122 if ([result hasPrefix: @"Unknown error"])
126 #if !TARGET_OS_IPHONE || defined(__SEC_TYPES__)
127 else if ($equal(domain,NSOSStatusErrorDomain)) {
128 // If it's an OSStatus, check whether CarbonCore knows its name:
129 const char *name = NULL;
130 #if !TARGET_OS_IPHONE
131 name = GetMacOSStatusErrorString(code);
134 result = [NSString stringWithCString: name encoding: NSMacOSRomanStringEncoding];
136 result = (id) SecCopyErrorMessageString(code,NULL);
138 [(id)CFMakeCollectable(result) autorelease];
139 if ([result hasPrefix: @"OSStatus "])
140 result = nil; // just a generic message
147 // Look up errors in string files keyed by the domain name:
148 NSString *table = [@"MYError_" stringByAppendingString: domain];
149 result = [[NSBundle mainBundle] localizedStringForKey: codeStr value: @"?" table: table];
150 if ([result isEqualToString: @"?"])
154 codeStr = $sprintf(@"%@ %@", MYShortErrorDomainName(domain), codeStr);;
155 return result ? $sprintf(@"%@ (%@)", result, codeStr) : codeStr;
161 @implementation NSError (MYUtils)
163 - (NSError*) my_errorByPrependingMessage: (NSString*)message
165 if( message.length ) {
166 NSDictionary *oldUserInfo = self.userInfo;
167 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
169 [userInfo addEntriesFromDictionary: oldUserInfo];
170 NSString *desc = [oldUserInfo objectForKey: NSLocalizedDescriptionKey];
172 message = $sprintf(@"%@: %@", message, desc);
173 [userInfo setObject: message forKey: NSLocalizedDescriptionKey];
174 return [NSError errorWithDomain: self.domain
181 - (NSString*) my_nameOfCode {
182 return MYErrorName(self.domain, self.code);
188 TestCase(MYErrorUtils) {
189 CAssertEqual(printableOSType('abcd'), @"abcd");
190 CAssertEqual(printableOSType(' '), @" ");
191 CAssertEqual(printableOSType(0x7e7e7e7e), @"~~~~");
192 CAssertEqual(printableOSType(0x7e7F7e7e), nil);
193 CAssertEqual(printableOSType(0x7e0D7e7e), nil);
194 CAssertEqual(printableOSType(0), nil);
195 CAssertEqual(printableOSType((OSType)-123456), nil);
197 CAssertEqual(MYErrorName(nil,0), nil);
198 CAssertEqual(MYErrorName(nil,12345), @"12345");
199 CAssertEqual(MYErrorName(nil,1), @"1");
200 CAssertEqual(MYErrorName(nil,-1), @"-1");
201 CAssertEqual(MYErrorName(nil,12345), @"12345");
202 CAssertEqual(MYErrorName(nil,-12345), @"-12345");
203 CAssertEqual(MYErrorName(nil,2147549184u), @"2147549184");
205 CAssertEqual(MYErrorName(@"foobar",0), nil);
206 CAssertEqual(MYErrorName(@"foobar",'fmt?'), @"foobar fmt?");
207 CAssertEqual(MYErrorName(@"foobar",1), @"foobar 1");
208 CAssertEqual(MYErrorName(@"FoobarErrorDomain",-1), @"Foobar -1");
209 CAssertEqual(MYErrorName(@"NSFoobarErrorDomain",12345), @"Foobar 12345");
212 err = [NSError errorWithDomain: NSPOSIXErrorDomain code: EPERM userInfo: nil];
213 CAssertEqual(err.my_nameOfCode, @"Operation not permitted (POSIX 1)");
214 err = [NSError errorWithDomain: NSPOSIXErrorDomain code: 12345 userInfo: nil];
215 CAssertEqual(err.my_nameOfCode, @"POSIX 12345");
217 #if !TARGET_OS_IPHONE
218 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: paramErr userInfo: nil];
219 CAssertEqual(err.my_nameOfCode, @"paramErr (OSStatus -50)");
220 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: fnfErr userInfo: nil];
221 CAssertEqual(err.my_nameOfCode, @"fnfErr (OSStatus -43)");
222 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25291 userInfo: nil];
223 CAssertEqual(err.my_nameOfCode, @"errKCNotAvailable / errSecNotAvailable (OSStatus -25291)");
224 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25260 userInfo: nil];
225 CAssertEqual(err.my_nameOfCode, @"Passphrase is required for import/export. (OSStatus -25260)");
227 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: 12345 userInfo: nil];
228 CAssertEqual(err.my_nameOfCode, @"OSStatus 12345");
230 err = [NSError errorWithDomain: @"CSSMErrorDomain" code: 2147549184u userInfo: nil];
231 CAssertEqual(err.my_nameOfCode, @"CSSM_CSSM_BASE_ERROR (CSSM 2147549184)");
233 err = [NSError errorWithDomain: (id)kCFErrorDomainCocoa code: 100 userInfo: nil];
234 CAssertEqual(err.my_nameOfCode, @"Cocoa 100");