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