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>
15 NSString* const MYErrorDomain = @"MYErrorDomain";
18 static NSError *MYMakeErrorV( int errorCode, NSString *domain, NSString *message, va_list args )
20 message = [[NSString alloc] initWithFormat: message arguments: args];
21 Log(@"MYError #%i: %@",errorCode,message);
22 NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
23 message, NSLocalizedDescriptionKey,
26 return [NSError errorWithDomain: domain
32 NSError *MYError( int errorCode, NSString *domain, NSString *message, ... )
35 va_start(args,message);
36 NSError *error = MYMakeErrorV(errorCode,domain,message,args);
42 BOOL MYMiscError( NSError **error, NSString *message, ... )
46 va_start(args,message);
47 *error = MYMakeErrorV(kMYErrorMisc,MYErrorDomain, message,args);
54 static NSString* printableOSType( OSType t ) {
55 if (t < 0x20202020 || t > 0x7e7e7e7e)
61 buf.ostype = CFSwapInt32HostToBig(t);
62 for (int i=0; i<4; i++)
63 if (buf.ch[i] < 0x20 || buf.ch[i] > 0x7E)
65 return [[[NSString alloc] initWithBytes: &buf.ch length: 4 encoding: NSMacOSRomanStringEncoding]
70 static NSString* printableErrorCode( NSInteger code ) {
72 return $sprintf(@"%u", code); // CSSM errors are huge unsigned values > 0x80000000
73 NSString *result = printableOSType((OSType)code);
75 return result; // CoreAudio errors are OSTypes (4-char strings)
76 return $sprintf(@"%i", code); // Default: OSStatus and errno values are signed
79 static NSString* MYShortErrorDomainName( NSString *domain ) {
80 if ([domain hasPrefix: @"kCFErrorDomain"])
81 domain = [domain substringFromIndex: 14];
83 if ([domain hasSuffix: @"ErrorDomain"])
84 domain = [domain substringToIndex: domain.length - 11];
85 if ([domain hasPrefix: @"NS"])
86 domain = [domain substringFromIndex: 2];
91 NSString* MYErrorName( NSString *domain, NSInteger code ) {
94 NSString *codeStr = printableErrorCode(code);
97 NSString *result = nil;
99 if ($equal(domain,NSPOSIXErrorDomain)) {
100 // Interpret POSIX errors via strerror
101 // (which unfortunately returns a description, not the name of the constant)
102 const char *name = strerror(code);
104 result = [NSString stringWithCString: name encoding: NSASCIIStringEncoding];
105 if ([result hasPrefix: @"Unknown error"])
109 #if !TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
110 else if ($equal(domain,NSOSStatusErrorDomain)) {
111 // If it's an OSStatus, check whether CarbonCore knows its name:
112 const char *name = NULL;
113 #if !TARGET_OS_IPHONE
114 name = GetMacOSStatusErrorString(code);
117 result = [NSString stringWithCString: name encoding: NSMacOSRomanStringEncoding];
119 result = (id) SecCopyErrorMessageString(code,NULL);
121 [(id)CFMakeCollectable(result) autorelease];
122 if ([result hasPrefix: @"OSStatus "])
123 result = nil; // just a generic message
130 // Look up errors in string files keyed by the domain name:
131 NSString *table = [@"MYError_" stringByAppendingString: domain];
132 result = [[NSBundle mainBundle] localizedStringForKey: codeStr value: @"?" table: table];
133 if ([result isEqualToString: @"?"])
137 codeStr = $sprintf(@"%@ %@", MYShortErrorDomainName(domain), codeStr);;
138 return result ? $sprintf(@"%@ (%@)", result, codeStr) : codeStr;
144 @implementation NSError (MYUtils)
146 - (NSError*) my_errorByPrependingMessage: (NSString*)message
148 if( message.length ) {
149 NSDictionary *oldUserInfo = self.userInfo;
150 NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
152 [userInfo addEntriesFromDictionary: oldUserInfo];
153 NSString *desc = [oldUserInfo objectForKey: NSLocalizedDescriptionKey];
155 message = $sprintf(@"%@: %@", message, desc);
156 [userInfo setObject: message forKey: NSLocalizedDescriptionKey];
157 return [NSError errorWithDomain: self.domain
164 - (NSString*) my_nameOfCode {
165 return MYErrorName(self.domain, self.code);
171 TestCase(MYErrorUtils) {
172 CAssertEqual(printableOSType('abcd'), @"abcd");
173 CAssertEqual(printableOSType(' '), @" ");
174 CAssertEqual(printableOSType(0x7e7e7e7e), @"~~~~");
175 CAssertEqual(printableOSType(0x7e7F7e7e), nil);
176 CAssertEqual(printableOSType(0x7e0D7e7e), nil);
177 CAssertEqual(printableOSType(0), nil);
178 CAssertEqual(printableOSType((OSType)-123456), nil);
180 CAssertEqual(MYErrorName(nil,0), nil);
181 CAssertEqual(MYErrorName(nil,12345), @"12345");
182 CAssertEqual(MYErrorName(nil,1), @"1");
183 CAssertEqual(MYErrorName(nil,-1), @"-1");
184 CAssertEqual(MYErrorName(nil,12345), @"12345");
185 CAssertEqual(MYErrorName(nil,-12345), @"-12345");
186 CAssertEqual(MYErrorName(nil,2147549184u), @"2147549184");
188 CAssertEqual(MYErrorName(@"foobar",0), nil);
189 CAssertEqual(MYErrorName(@"foobar",'fmt?'), @"foobar fmt?");
190 CAssertEqual(MYErrorName(@"foobar",1), @"foobar 1");
191 CAssertEqual(MYErrorName(@"FoobarErrorDomain",-1), @"Foobar -1");
192 CAssertEqual(MYErrorName(@"NSFoobarErrorDomain",12345), @"Foobar 12345");
195 err = [NSError errorWithDomain: NSPOSIXErrorDomain code: EPERM userInfo: nil];
196 CAssertEqual(err.my_nameOfCode, @"Operation not permitted (POSIX 1)");
197 err = [NSError errorWithDomain: NSPOSIXErrorDomain code: 12345 userInfo: nil];
198 CAssertEqual(err.my_nameOfCode, @"POSIX 12345");
200 #if !TARGET_OS_IPHONE
201 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: paramErr userInfo: nil];
202 CAssertEqual(err.my_nameOfCode, @"paramErr (OSStatus -50)");
203 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: fnfErr userInfo: nil];
204 CAssertEqual(err.my_nameOfCode, @"fnfErr (OSStatus -43)");
205 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25291 userInfo: nil];
206 CAssertEqual(err.my_nameOfCode, @"errKCNotAvailable / errSecNotAvailable (OSStatus -25291)");
207 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25260 userInfo: nil];
208 CAssertEqual(err.my_nameOfCode, @"Passphrase is required for import/export. (OSStatus -25260)");
210 err = [NSError errorWithDomain: NSOSStatusErrorDomain code: 12345 userInfo: nil];
211 CAssertEqual(err.my_nameOfCode, @"OSStatus 12345");
213 err = [NSError errorWithDomain: @"CSSMErrorDomain" code: 2147549184u userInfo: nil];
214 CAssertEqual(err.my_nameOfCode, @"CSSM_CSSM_BASE_ERROR (CSSM 2147549184)");
216 err = [NSError errorWithDomain: (id)kCFErrorDomainCocoa code: 100 userInfo: nil];
217 CAssertEqual(err.my_nameOfCode, @"Cocoa 100");