MYErrorUtils.m
author Jens Alfke <jens@mooseyard.com>
Sun May 10 18:57:43 2009 -0700 (2009-05-10)
changeset 29 8874aff14cc9
parent 27 256370e8935a
permissions -rw-r--r--
* Added kv*Set utility functions for handling KV grunge when mutating an NSSet property.
* Change CFMakeCollectable to NSMakeCollectable.
     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 #if USE_SECURITY_API
    15 #import <Security/SecBase.h>
    16 #endif
    17 
    18 
    19 NSString* const MYErrorDomain = @"MYErrorDomain";
    20 
    21 
    22 static NSError *MYMakeErrorV( int errorCode, NSString *domain, NSString *message, va_list args )
    23 {
    24     message = [[NSString alloc] initWithFormat: message arguments: args];
    25     NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
    26                                                       message, NSLocalizedDescriptionKey,
    27                                                       nil];
    28     [message release];
    29     return [NSError errorWithDomain: domain
    30                                code: errorCode
    31                            userInfo: userInfo];
    32 }
    33 
    34 
    35 NSError *MYError( int errorCode, NSString *domain, NSString *message, ... )
    36 {
    37     va_list args;
    38     va_start(args,message);
    39     NSError *error = MYMakeErrorV(errorCode,domain,message,args);
    40     va_end(args);
    41     return error;
    42 }
    43 
    44 
    45 BOOL MYReturnError( NSError **outError,
    46                     int errorCode, NSString *domain, NSString *messageFormat, ... ) 
    47 {
    48     if (errorCode) {
    49         if (outError) {
    50             va_list args;
    51             va_start(args,messageFormat);
    52             *outError = MYMakeErrorV(errorCode, domain, messageFormat, args);
    53             va_end(args);
    54             Log(@"MYReturnError: %@",*outError);
    55         } else
    56             Log(@"MYReturnError: %@/%i",domain,errorCode);
    57         return NO;
    58     } else
    59         return YES;
    60 }
    61 
    62 
    63 BOOL MYMiscError( NSError **error, NSString *message, ... )
    64 {
    65     if (error) {
    66         va_list args;
    67         va_start(args,message);
    68         *error = MYMakeErrorV(kMYErrorMisc,MYErrorDomain, message,args);
    69         va_end(args);
    70     }
    71     return NO;
    72 }
    73 
    74 
    75 static NSString* printableOSType( OSType t ) {
    76     if (t < 0x20202020 || t > 0x7e7e7e7e)
    77         return nil;
    78     union {
    79         OSType ostype;
    80         unsigned char ch[4];
    81     } buf;
    82     buf.ostype = CFSwapInt32HostToBig(t);
    83     for (int i=0; i<4; i++)
    84         if (buf.ch[i] < 0x20 || buf.ch[i] > 0x7E)
    85             return nil;
    86     return [[[NSString alloc] initWithBytes: &buf.ch length: 4 encoding: NSMacOSRomanStringEncoding]
    87             autorelease];
    88 }
    89 
    90 
    91 static NSString* printableErrorCode( NSInteger code ) {
    92     if (code < -99999)
    93         return $sprintf(@"%u", code);       // CSSM errors are huge unsigned values > 0x80000000
    94     NSString *result = printableOSType((OSType)code);
    95     if (result)
    96         return result;                      // CoreAudio errors are OSTypes (4-char strings)
    97     return $sprintf(@"%i", code);           // Default: OSStatus and errno values are signed
    98 }
    99 
   100 static NSString* MYShortErrorDomainName( NSString *domain ) {
   101     if ([domain hasPrefix: @"kCFErrorDomain"])
   102         domain = [domain substringFromIndex: 14];
   103     else {
   104         if ([domain hasSuffix: @"ErrorDomain"])
   105             domain = [domain substringToIndex: domain.length - 11];
   106         if ([domain hasPrefix: @"NS"])
   107             domain = [domain substringFromIndex: 2];
   108     }
   109     return domain;
   110 }
   111 
   112 NSString* MYErrorName( NSString *domain, NSInteger code ) {
   113     if (code == 0)
   114         return nil;
   115     NSString *codeStr = printableErrorCode(code);
   116     if (!domain)
   117         return codeStr;
   118     NSString *result = nil;
   119     
   120     if ($equal(domain,NSPOSIXErrorDomain)) {
   121         // Interpret POSIX errors via strerror
   122         // (which unfortunately returns a description, not the name of the constant)
   123         const char *name = strerror(code);
   124         if (name) {
   125             result = [NSString stringWithCString: name encoding: NSASCIIStringEncoding];
   126             if ([result hasPrefix: @"Unknown error"])
   127                 result = nil;
   128         }
   129     } 
   130 #if !TARGET_OS_IPHONE || defined(__SEC_TYPES__)
   131     else if ($equal(domain,NSOSStatusErrorDomain)) {
   132         // If it's an OSStatus, check whether CarbonCore knows its name:
   133         const char *name = NULL;
   134 #if !TARGET_OS_IPHONE
   135         name = GetMacOSStatusErrorString(code);
   136 #endif
   137         if (name && *name)
   138             result = [NSString stringWithCString: name encoding: NSMacOSRomanStringEncoding];
   139         else {
   140 #if USE_SECURITY_API
   141             result = (id) SecCopyErrorMessageString(code,NULL);
   142             if (result) {
   143                 [NSMakeCollectable(result) autorelease];
   144                 if ([result hasPrefix: @"OSStatus "])
   145                     result = nil; // just a generic message
   146             }
   147 #endif
   148         }
   149     }
   150 #endif
   151     
   152     if (!result) {
   153         // Look up errors in string files keyed by the domain name:
   154         NSString *table = [@"MYError_" stringByAppendingString: domain];
   155         result = [[NSBundle mainBundle] localizedStringForKey: codeStr value: @"?" table: table];
   156         if ([result isEqualToString: @"?"])
   157             result = nil;
   158     }
   159     
   160     codeStr = $sprintf(@"%@ %@", MYShortErrorDomainName(domain), codeStr);;
   161     return result ? $sprintf(@"%@ (%@)", result, codeStr) : codeStr;
   162 }
   163 
   164 
   165 
   166 
   167 @implementation NSError (MYUtils)
   168 
   169 - (NSError*) my_errorByPrependingMessage: (NSString*)message
   170 {
   171     if( message.length ) {
   172         NSDictionary *oldUserInfo = self.userInfo;
   173         NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
   174         if( oldUserInfo )
   175             [userInfo addEntriesFromDictionary: oldUserInfo];
   176         NSString *desc = [oldUserInfo objectForKey: NSLocalizedDescriptionKey];
   177         if( desc )
   178             message = $sprintf(@"%@: %@", message, desc);
   179         [userInfo setObject: message forKey: NSLocalizedDescriptionKey];
   180         return [NSError errorWithDomain: self.domain
   181                                    code: self.code
   182                                userInfo: userInfo];
   183     } else
   184         return self;
   185 }
   186 
   187 - (NSString*) my_nameOfCode {
   188     return MYErrorName(self.domain, self.code);
   189 }
   190 
   191 @end
   192 
   193 
   194 TestCase(MYErrorUtils) {
   195     CAssertEqual(printableOSType('abcd'), @"abcd");
   196     CAssertEqual(printableOSType('    '), @"    ");
   197     CAssertEqual(printableOSType(0x7e7e7e7e), @"~~~~");
   198     CAssertEqual(printableOSType(0x7e7F7e7e), nil);
   199     CAssertEqual(printableOSType(0x7e0D7e7e), nil);
   200     CAssertEqual(printableOSType(0), nil);
   201     CAssertEqual(printableOSType((OSType)-123456), nil);
   202 
   203     CAssertEqual(MYErrorName(nil,0),      nil);
   204     CAssertEqual(MYErrorName(nil,12345),  @"12345");
   205     CAssertEqual(MYErrorName(nil,1),      @"1");
   206     CAssertEqual(MYErrorName(nil,-1),     @"-1");
   207     CAssertEqual(MYErrorName(nil,12345),  @"12345");
   208     CAssertEqual(MYErrorName(nil,-12345), @"-12345");
   209     CAssertEqual(MYErrorName(nil,2147549184u), @"2147549184");
   210     
   211     CAssertEqual(MYErrorName(@"foobar",0), nil);
   212     CAssertEqual(MYErrorName(@"foobar",'fmt?'), @"foobar fmt?");
   213     CAssertEqual(MYErrorName(@"foobar",1), @"foobar 1");
   214     CAssertEqual(MYErrorName(@"FoobarErrorDomain",-1), @"Foobar -1");
   215     CAssertEqual(MYErrorName(@"NSFoobarErrorDomain",12345), @"Foobar 12345");
   216 
   217     NSError *err;
   218     err = [NSError errorWithDomain: NSPOSIXErrorDomain code: EPERM userInfo: nil];
   219     CAssertEqual(err.my_nameOfCode, @"Operation not permitted (POSIX 1)");
   220     err = [NSError errorWithDomain: NSPOSIXErrorDomain code: 12345 userInfo: nil];
   221     CAssertEqual(err.my_nameOfCode, @"POSIX 12345");
   222     
   223 #if !TARGET_OS_IPHONE
   224     err = [NSError errorWithDomain: NSOSStatusErrorDomain code: paramErr userInfo: nil];
   225     CAssertEqual(err.my_nameOfCode, @"paramErr (OSStatus -50)");
   226     err = [NSError errorWithDomain: NSOSStatusErrorDomain code: fnfErr userInfo: nil];
   227     CAssertEqual(err.my_nameOfCode, @"fnfErr (OSStatus -43)");
   228     err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25291 userInfo: nil];
   229     CAssertEqual(err.my_nameOfCode, @"errKCNotAvailable / errSecNotAvailable (OSStatus -25291)");
   230     err = [NSError errorWithDomain: NSOSStatusErrorDomain code: -25260 userInfo: nil];
   231     CAssertEqual(err.my_nameOfCode, @"Passphrase is required for import/export. (OSStatus -25260)");
   232 #endif
   233     err = [NSError errorWithDomain: NSOSStatusErrorDomain code: 12345 userInfo: nil];
   234     CAssertEqual(err.my_nameOfCode, @"OSStatus 12345");
   235 
   236     err = [NSError errorWithDomain: @"CSSMErrorDomain" code: 2147549184u userInfo: nil];
   237     CAssertEqual(err.my_nameOfCode, @"CSSM_CSSM_BASE_ERROR (CSSM 2147549184)");
   238 
   239     err = [NSError errorWithDomain: (id)kCFErrorDomainCocoa code: 100 userInfo: nil];
   240     CAssertEqual(err.my_nameOfCode, @"Cocoa 100");
   241 }