MYErrorUtils.m
author snej@snej-mbp.mtv.corp.google.com
Tue Apr 07 11:13:25 2009 -0700 (2009-04-07)
changeset 23 a910102a1c9d
parent 22 a9da6c5d3f7c
child 25 47d10ac2d04e
permissions -rw-r--r--
* Added MYReturnError.
* Better iPhone support in ExceptionUtils.
* Make sure "All Tests Passed/Failed" message is always logged.
     1 //
     2 //  MYErrorUtils.m
     3 //  MYCrypto
     4 //
     5 //  Created by Jens Alfke on 2/25/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYErrorUtils.h"
    10 #import "Test.h"
    11 #import "CollectionUtils.h"
    12 #import <Foundation/Foundation.h>
    13 #import <Security/SecBase.h>
    14 
    15 
    16 NSString* const MYErrorDomain = @"MYErrorDomain";
    17 
    18 
    19 static NSError *MYMakeErrorV( int errorCode, NSString *domain, NSString *message, va_list args )
    20 {
    21     message = [[NSString alloc] initWithFormat: message arguments: args];
    22     Log(@"MYError #%i: %@",errorCode,message);
    23     NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
    24                               message, NSLocalizedDescriptionKey,
    25                               nil];
    26     [message release];
    27     return [NSError errorWithDomain: domain
    28                                code: errorCode
    29                            userInfo: userInfo];
    30 }
    31 
    32 
    33 NSError *MYError( int errorCode, NSString *domain, NSString *message, ... )
    34 {
    35     va_list args;
    36     va_start(args,message);
    37     NSError *error = MYMakeErrorV(errorCode,domain,message,args);
    38     va_end(args);
    39     return error;
    40 }
    41 
    42 
    43 BOOL MYReturnError( NSError **outError,
    44                     int errorCode, NSString *domain, NSString *messageFormat, ... ) 
    45 {
    46     if (errorCode) {
    47         if (outError) {
    48             va_list args;
    49             va_start(args,messageFormat);
    50             *outError = MYMakeErrorV(errorCode, domain, messageFormat, args);
    51             va_end(args);
    52         }
    53         return NO;
    54     } else
    55         return YES;
    56 }
    57 
    58 
    59 BOOL MYMiscError( NSError **error, NSString *message, ... )
    60 {
    61     if (error) {
    62         va_list args;
    63         va_start(args,message);
    64         *error = MYMakeErrorV(kMYErrorMisc,MYErrorDomain, message,args);
    65         va_end(args);
    66     }
    67     return NO;
    68 }
    69 
    70 
    71 static NSString* printableOSType( OSType t ) {
    72     if (t < 0x20202020 || t > 0x7e7e7e7e)
    73         return nil;
    74     union {
    75         OSType ostype;
    76         unsigned char ch[4];
    77     } buf;
    78     buf.ostype = CFSwapInt32HostToBig(t);
    79     for (int i=0; i<4; i++)
    80         if (buf.ch[i] < 0x20 || buf.ch[i] > 0x7E)
    81             return nil;
    82     return [[[NSString alloc] initWithBytes: &buf.ch length: 4 encoding: NSMacOSRomanStringEncoding]
    83             autorelease];
    84 }
    85 
    86 
    87 static NSString* printableErrorCode( NSInteger code ) {
    88     if (code < -99999)
    89         return $sprintf(@"%u", code);       // CSSM errors are huge unsigned values > 0x80000000
    90     NSString *result = printableOSType((OSType)code);
    91     if (result)
    92         return result;                      // CoreAudio errors are OSTypes (4-char strings)
    93     return $sprintf(@"%i", code);           // Default: OSStatus and errno values are signed
    94 }
    95 
    96 static NSString* MYShortErrorDomainName( NSString *domain ) {
    97     if ([domain hasPrefix: @"kCFErrorDomain"])
    98         domain = [domain substringFromIndex: 14];
    99     else {
   100         if ([domain hasSuffix: @"ErrorDomain"])
   101             domain = [domain substringToIndex: domain.length - 11];
   102         if ([domain hasPrefix: @"NS"])
   103             domain = [domain substringFromIndex: 2];
   104     }
   105     return domain;
   106 }
   107 
   108 NSString* MYErrorName( NSString *domain, NSInteger code ) {
   109     if (code == 0)
   110         return nil;
   111     NSString *codeStr = printableErrorCode(code);
   112     if (!domain)
   113         return codeStr;
   114     NSString *result = nil;
   115     
   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);
   120         if (name) {
   121             result = [NSString stringWithCString: name encoding: NSASCIIStringEncoding];
   122             if ([result hasPrefix: @"Unknown error"])
   123                 result = nil;
   124         }
   125     } 
   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);
   132 #endif
   133         if (name && *name)
   134             result = [NSString stringWithCString: name encoding: NSMacOSRomanStringEncoding];
   135         else {
   136             result = (id) SecCopyErrorMessageString(code,NULL);
   137             if (result) {
   138                 [(id)CFMakeCollectable(result) autorelease];
   139                 if ([result hasPrefix: @"OSStatus "])
   140                     result = nil; // just a generic message
   141             }
   142         }
   143     }
   144 #endif
   145     
   146     if (!result) {
   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: @"?"])
   151             result = nil;
   152     }
   153     
   154     codeStr = $sprintf(@"%@ %@", MYShortErrorDomainName(domain), codeStr);;
   155     return result ? $sprintf(@"%@ (%@)", result, codeStr) : codeStr;
   156 }
   157 
   158 
   159 
   160 
   161 @implementation NSError (MYUtils)
   162 
   163 - (NSError*) my_errorByPrependingMessage: (NSString*)message
   164 {
   165     if( message.length ) {
   166         NSDictionary *oldUserInfo = self.userInfo;
   167         NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
   168         if( oldUserInfo )
   169             [userInfo addEntriesFromDictionary: oldUserInfo];
   170         NSString *desc = [oldUserInfo objectForKey: NSLocalizedDescriptionKey];
   171         if( desc )
   172             message = $sprintf(@"%@: %@", message, desc);
   173         [userInfo setObject: message forKey: NSLocalizedDescriptionKey];
   174         return [NSError errorWithDomain: self.domain
   175                                    code: self.code
   176                                userInfo: userInfo];
   177     } else
   178         return self;
   179 }
   180 
   181 - (NSString*) my_nameOfCode {
   182     return MYErrorName(self.domain, self.code);
   183 }
   184 
   185 @end
   186 
   187 
   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);
   196 
   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");
   204     
   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");
   210 
   211     NSError *err;
   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");
   216     
   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)");
   226 #endif
   227     err = [NSError errorWithDomain: NSOSStatusErrorDomain code: 12345 userInfo: nil];
   228     CAssertEqual(err.my_nameOfCode, @"OSStatus 12345");
   229 
   230     err = [NSError errorWithDomain: @"CSSMErrorDomain" code: 2147549184u userInfo: nil];
   231     CAssertEqual(err.my_nameOfCode, @"CSSM_CSSM_BASE_ERROR (CSSM 2147549184)");
   232 
   233     err = [NSError errorWithDomain: (id)kCFErrorDomainCocoa code: 100 userInfo: nil];
   234     CAssertEqual(err.my_nameOfCode, @"Cocoa 100");
   235 }