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