MYErrorUtils.m
author snej@snej.local
Sat Apr 04 20:53:53 2009 -0700 (2009-04-04)
changeset 22 a9da6c5d3f7c
child 23 a910102a1c9d
permissions -rw-r--r--
* Added MYErrorUtils
* Added AssertAbstractMethod() to Test
* Added .xcconfig files
     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 
    14 
    15 NSString* const MYErrorDomain = @"MYErrorDomain";
    16 
    17 
    18 static NSError *MYMakeErrorV( int errorCode, NSString *domain, NSString *message, va_list args )
    19 {
    20     message = [[NSString alloc] initWithFormat: message arguments: args];
    21     Log(@"MYError #%i: %@",errorCode,message);
    22     NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
    23                               message, NSLocalizedDescriptionKey,
    24                               nil];
    25     [message release];
    26     return [NSError errorWithDomain: domain
    27                                code: errorCode
    28                            userInfo: userInfo];
    29 }
    30 
    31 
    32 NSError *MYError( int errorCode, NSString *domain, NSString *message, ... )
    33 {
    34     va_list args;
    35     va_start(args,message);
    36     NSError *error = MYMakeErrorV(errorCode,domain,message,args);
    37     va_end(args);
    38     return error;
    39 }
    40 
    41 
    42 BOOL MYMiscError( NSError **error, NSString *message, ... )
    43 {
    44     if (error) {
    45         va_list args;
    46         va_start(args,message);
    47         *error = MYMakeErrorV(kMYErrorMisc,MYErrorDomain, message,args);
    48         va_end(args);
    49     }
    50     return NO;
    51 }
    52 
    53 
    54 static NSString* printableOSType( OSType t ) {
    55     if (t < 0x20202020 || t > 0x7e7e7e7e)
    56         return nil;
    57     union {
    58         OSType ostype;
    59         unsigned char ch[4];
    60     } buf;
    61     buf.ostype = CFSwapInt32HostToBig(t);
    62     for (int i=0; i<4; i++)
    63         if (buf.ch[i] < 0x20 || buf.ch[i] > 0x7E)
    64             return nil;
    65     return [[[NSString alloc] initWithBytes: &buf.ch length: 4 encoding: NSMacOSRomanStringEncoding]
    66             autorelease];
    67 }
    68 
    69 
    70 static NSString* printableErrorCode( NSInteger code ) {
    71     if (code < -99999)
    72         return $sprintf(@"%u", code);       // CSSM errors are huge unsigned values > 0x80000000
    73     NSString *result = printableOSType((OSType)code);
    74     if (result)
    75         return result;                      // CoreAudio errors are OSTypes (4-char strings)
    76     return $sprintf(@"%i", code);           // Default: OSStatus and errno values are signed
    77 }
    78 
    79 static NSString* MYShortErrorDomainName( NSString *domain ) {
    80     if ([domain hasPrefix: @"kCFErrorDomain"])
    81         domain = [domain substringFromIndex: 14];
    82     else {
    83         if ([domain hasSuffix: @"ErrorDomain"])
    84             domain = [domain substringToIndex: domain.length - 11];
    85         if ([domain hasPrefix: @"NS"])
    86             domain = [domain substringFromIndex: 2];
    87     }
    88     return domain;
    89 }
    90 
    91 NSString* MYErrorName( NSString *domain, NSInteger code ) {
    92     if (code == 0)
    93         return nil;
    94     NSString *codeStr = printableErrorCode(code);
    95     if (!domain)
    96         return codeStr;
    97     NSString *result = nil;
    98     
    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);
   103         if (name) {
   104             result = [NSString stringWithCString: name encoding: NSASCIIStringEncoding];
   105             if ([result hasPrefix: @"Unknown error"])
   106                 result = nil;
   107         }
   108     } 
   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);
   115 #endif
   116         if (name && *name)
   117             result = [NSString stringWithCString: name encoding: NSMacOSRomanStringEncoding];
   118         else {
   119             result = (id) SecCopyErrorMessageString(code,NULL);
   120             if (result) {
   121                 [(id)CFMakeCollectable(result) autorelease];
   122                 if ([result hasPrefix: @"OSStatus "])
   123                     result = nil; // just a generic message
   124             }
   125         }
   126     }
   127 #endif
   128     
   129     if (!result) {
   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: @"?"])
   134             result = nil;
   135     }
   136     
   137     codeStr = $sprintf(@"%@ %@", MYShortErrorDomainName(domain), codeStr);;
   138     return result ? $sprintf(@"%@ (%@)", result, codeStr) : codeStr;
   139 }
   140 
   141 
   142 
   143 
   144 @implementation NSError (MYUtils)
   145 
   146 - (NSError*) my_errorByPrependingMessage: (NSString*)message
   147 {
   148     if( message.length ) {
   149         NSDictionary *oldUserInfo = self.userInfo;
   150         NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
   151         if( oldUserInfo )
   152             [userInfo addEntriesFromDictionary: oldUserInfo];
   153         NSString *desc = [oldUserInfo objectForKey: NSLocalizedDescriptionKey];
   154         if( desc )
   155             message = $sprintf(@"%@: %@", message, desc);
   156         [userInfo setObject: message forKey: NSLocalizedDescriptionKey];
   157         return [NSError errorWithDomain: self.domain
   158                                    code: self.code
   159                                userInfo: userInfo];
   160     } else
   161         return self;
   162 }
   163 
   164 - (NSString*) my_nameOfCode {
   165     return MYErrorName(self.domain, self.code);
   166 }
   167 
   168 @end
   169 
   170 
   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);
   179 
   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");
   187     
   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");
   193 
   194     NSError *err;
   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");
   199     
   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)");
   209 #endif
   210     err = [NSError errorWithDomain: NSOSStatusErrorDomain code: 12345 userInfo: nil];
   211     CAssertEqual(err.my_nameOfCode, @"OSStatus 12345");
   212 
   213     err = [NSError errorWithDomain: @"CSSMErrorDomain" code: 2147549184u userInfo: nil];
   214     CAssertEqual(err.my_nameOfCode, @"CSSM_CSSM_BASE_ERROR (CSSM 2147549184)");
   215 
   216     err = [NSError errorWithDomain: (id)kCFErrorDomainCocoa code: 100 userInfo: nil];
   217     CAssertEqual(err.my_nameOfCode, @"Cocoa 100");
   218 }