LCOV - code coverage report
Current view: top level - Core/OXPVerifier - OOPListSchemaVerifier.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 83 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          Line data    Source code
       1           0 : /*
       2             : 
       3             : OOPListSchemaVerifier.m
       4             : 
       5             : 
       6             : Copyright (C) 2007-2013 Jens Ayton
       7             : 
       8             : Permission is hereby granted, free of charge, to any person obtaining a copy
       9             : of this software and associated documentation files (the "Software"), to deal
      10             : in the Software without restriction, including without limitation the rights
      11             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      12             : copies of the Software, and to permit persons to whom the Software is
      13             : furnished to do so, subject to the following conditions:
      14             : 
      15             : The above copyright notice and this permission notice shall be included in all
      16             : copies or substantial portions of the Software.
      17             : 
      18             : THE SOFTWARE IS PROVIDED ìAS ISî, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      19             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      20             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      21             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      22             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      23             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      24             : SOFTWARE.
      25             : 
      26             : */
      27             : 
      28             : #import "OOPListSchemaVerifier.h"
      29             : 
      30             : #if OO_OXP_VERIFIER_ENABLED
      31             : 
      32             : #import "OOLoggingExtended.h"
      33             : #import "OOCollectionExtractors.h"
      34             : #import "OOMaths.h"
      35             : #include <limits.h>
      36             : 
      37             : 
      38           0 : #define PLIST_VERIFIER_DEBUG_DUMP_ENABLED               1
      39             : 
      40             : 
      41           0 : enum
      42             : {
      43             :         // Largest allowable number of characters for string included in error message.
      44             :         kMaximumLengthForStringInErrorMessage           = 100
      45             : };
      46             : 
      47             : 
      48             : // Internal error codes.
      49           0 : enum
      50             : {
      51             :         kStartOfPrivateErrorCodes = kPListErrorLastErrorCode,
      52             :         
      53             :         kPListErrorFailedAndErrorHasBeenReported
      54             : };
      55             : 
      56             : 
      57             : #if PLIST_VERIFIER_DEBUG_DUMP_ENABLED
      58           0 : static BOOL                             sDebugDump = NO;
      59             : 
      60           0 : #define DebugDumpIndent()               do { if (sDebugDump) OOLogIndent(); } while (0)
      61           0 : #define DebugDumpOutdent()              do { if (sDebugDump) OOLogOutdent(); } while (0)
      62           0 : #define DebugDumpPushIndent()   do { if (sDebugDump) OOLogPushIndent(); } while (0)
      63           0 : #define DebugDumpPopIndent()    do { if (sDebugDump) OOLogPopIndent(); } while (0)
      64           0 : #define DebugDump(...)                  do { if (sDebugDump) OOLog(@"verifyOXP.verbose.plistDebugDump", __VA_ARGS__); } while (0)
      65             : #else
      66             : #define DebugDumpIndent()               do { } while (0)
      67             : #define DebugDumpOutdent()              do { } while (0)
      68             : #define DebugDumpPushIndent()   do { } while (0)
      69             : #define DebugDumpPopIndent()    do { } while (0)
      70             : #define DebugDump(...)                  do { } while (0)
      71             : #endif
      72             : 
      73             : 
      74           0 : NSString * const kOOPListSchemaVerifierErrorDomain = @"org.aegidian.oolite.OOPListSchemaVerifier.ErrorDomain";
      75             : 
      76           0 : NSString * const kPListKeyPathErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier plist key path";
      77           0 : NSString * const kSchemaKeyPathErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier schema key path";
      78             : 
      79           0 : NSString * const kExpectedClassErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier expected class";
      80           0 : NSString * const kExpectedClassNameErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier expected class name";
      81           0 : NSString * const kUnknownKeyErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown key";
      82           0 : NSString * const kMissingRequiredKeysErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier missing required keys";
      83           0 : NSString * const kMissingSubStringErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier missing substring";
      84           0 : NSString * const kUnnownFilterErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown filter";
      85           0 : NSString * const kErrorsByOptionErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier errors by option";
      86             : 
      87           0 : NSString * const kUnknownTypeErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown type";
      88           0 : NSString * const kUndefinedMacroErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier undefined macro";
      89             : 
      90             : 
      91           0 : typedef enum
      92             : {
      93             :         kTypeUnknown,
      94             :         kTypeString,
      95             :         kTypeArray,
      96             :         kTypeDictionary,
      97             :         kTypeInteger,
      98             :         kTypePositiveInteger,
      99             :         kTypeFloat,
     100             :         kTypePositiveFloat,
     101             :         kTypeOneOf,
     102             :         kTypeEnumeration,
     103             :         kTypeBoolean,
     104             :         kTypeFuzzyBoolean,
     105             :         kTypeVector,
     106             :         kTypeQuaternion,
     107             :         kTypeDelegatedType
     108             : } SchemaType;
     109             : 
     110             : 
     111           0 : typedef struct BackLinkChain BackLinkChain;
     112           0 : struct BackLinkChain
     113             : {
     114           0 :         BackLinkChain                   *link;
     115           0 :         id                                              element;
     116             : };
     117             : 
     118           0 : OOINLINE BackLinkChain BackLink(BackLinkChain *link, id element)
     119             : {
     120             :         BackLinkChain result = { link, element };
     121             :         return result;
     122             : }
     123             : 
     124           0 : OOINLINE BackLinkChain BackLinkIndex(BackLinkChain *link, NSUInteger index)
     125             : {
     126             :         BackLinkChain result = { link, [NSNumber numberWithInteger:index] };
     127             :         return result;
     128             : }
     129             : 
     130           0 : OOINLINE BackLinkChain BackLinkRoot(void)
     131             : {
     132             :         BackLinkChain result = { NULL, NULL };
     133             :         return result;
     134             : }
     135             : 
     136             : 
     137             : static SchemaType StringToSchemaType(NSString *string, NSError **outError);
     138             : static NSString *ApplyStringFilter(NSString *string, id filterSpec, BackLinkChain keyPath, NSError **outError);
     139             : static BOOL ApplyStringTest(NSString *string, id test, SEL testSelector, NSString *testDescription, BackLinkChain keyPath, NSError **outError);
     140             : static NSArray *KeyPathToArray(BackLinkChain keyPath);
     141             : static NSString *KeyPathToString(BackLinkChain keyPath);
     142             : static NSString *StringForErrorReport(NSString *string);
     143             : static NSString *ArrayForErrorReport(NSArray *array);
     144             : static NSString *SetForErrorReport(NSSet *set);
     145             : static NSString *StringOrArrayForErrorReport(id value, NSString *arrayPrefix);
     146             : 
     147             : static NSError *Error(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *format, ...);
     148             : static NSError *ErrorWithProperty(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *propKey, id propValue, NSString *format, ...);
     149             : static NSError *ErrorWithDictionary(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, ...);
     150             : static NSError *ErrorWithDictionaryAndArguments(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, va_list arguments);
     151             : 
     152             : static NSError *ErrorTypeMismatch(Class expectedClass, NSString *expectedClassName, id actualObject, BackLinkChain keyPath);
     153             : static NSError *ErrorFailureAlreadyReported(void);
     154             : static BOOL IsFailureAlreadyReportedError(NSError *error);
     155             : 
     156             : 
     157             : @interface OOPListSchemaVerifier (OOPrivate)
     158             : 
     159             : // Call delegate methods.
     160           0 : - (BOOL)delegateVerifierWithPropertyList:(id)rootPList
     161             :                                                                    named:(NSString *)name
     162             :                                                         testProperty:(id)subPList
     163             :                                                                   atPath:(BackLinkChain)keyPath
     164             :                                                          againstType:(NSString *)typeKey
     165             :                                                                    error:(NSError **)outError;
     166             : 
     167           0 : - (BOOL)delegateVerifierWithPropertyList:(id)rootPList
     168             :                                                                    named:(NSString *)name
     169             :                                            failedForProperty:(id)subPList
     170             :                                                            withError:(NSError *)error
     171             :                                                         expectedType:(NSDictionary *)localSchema;
     172             : 
     173           0 : - (BOOL)verifyPList:(id)rootPList
     174             :                           named:(NSString *)name
     175             :                 subProperty:(id)subProperty
     176             :   againstSchemaType:(id)subSchema
     177             :                          atPath:(BackLinkChain)keyPath
     178             :                   tentative:(BOOL)tentative
     179             :                           error:(NSError **)outError
     180             :                            stop:(BOOL *)outStop;
     181             : 
     182           0 : - (NSDictionary *)resolveSchemaType:(id)specifier
     183             :                                                          atPath:(BackLinkChain)keyPath
     184             :                                                           error:(NSError **)outError;
     185             : 
     186             : @end
     187             : 
     188             : 
     189             : @interface NSString (OOPListSchemaVerifierHelpers)
     190             : 
     191           0 : - (BOOL)ooPListVerifierHasSubString:(NSString *)string;
     192             : 
     193             : @end
     194             : 
     195             : 
     196           0 : #define VERIFY_PROTO(T) static NSError *Verify_##T(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
     197           0 : VERIFY_PROTO(String);
     198           0 : VERIFY_PROTO(Array);
     199           0 : VERIFY_PROTO(Dictionary);
     200           0 : VERIFY_PROTO(Integer);
     201           0 : VERIFY_PROTO(PositiveInteger);
     202           0 : VERIFY_PROTO(Float);
     203           0 : VERIFY_PROTO(PositiveFloat);
     204           0 : VERIFY_PROTO(OneOf);
     205           0 : VERIFY_PROTO(Enumeration);
     206           0 : VERIFY_PROTO(Boolean);
     207           0 : VERIFY_PROTO(FuzzyBoolean);
     208           0 : VERIFY_PROTO(Vector);
     209           0 : VERIFY_PROTO(Quaternion);
     210           0 : VERIFY_PROTO(DelegatedType);
     211             : 
     212             : 
     213             : @implementation OOPListSchemaVerifier
     214             : 
     215             : + (id)verifierWithSchema:(NSDictionary *)schema
     216             : {
     217             :         return [[[self alloc] initWithSchema:schema] autorelease];
     218             : }
     219             : 
     220             : 
     221             : - (id)initWithSchema:(NSDictionary *)schema
     222             : {
     223             :         self = [super init];
     224             :         if (self != nil)
     225             :         {
     226             :                 _schema = [schema retain];
     227             :                 _definitions = [[_schema oo_dictionaryForKey:@"$definitions"] retain];
     228             :                 sDebugDump = [[NSUserDefaults standardUserDefaults] boolForKey:@"plist-schema-verifier-dump-structure"];
     229             :                 if (sDebugDump)  OOLogSetDisplayMessagesInClass(@"verifyOXP.verbose.plistDebugDump", YES);
     230             :                 
     231             :                 if (_schema == nil)
     232             :                 {
     233             :                         [self release];
     234             :                         self = nil;
     235             :                 }
     236             :         }
     237             :         
     238             :         return self;
     239             : }
     240             : 
     241             : 
     242           0 : - (void)dealloc
     243             : {
     244             :         [_schema release];
     245             :         [_definitions release];
     246             :         
     247             :         [super dealloc];
     248             : }
     249             : 
     250             : 
     251             : - (void)setDelegate:(id)delegate
     252             : {
     253             :         if (_delegate != delegate)
     254             :         {
     255             :                 _delegate = delegate;
     256             :                 _badDelegateWarning = NO;
     257             :         }
     258             : }
     259             : 
     260             : 
     261             : - (id)delegate
     262             : {
     263             :         return _delegate;
     264             : }
     265             : 
     266             : 
     267             : - (BOOL)verifyPropertyList:(id)plist named:(NSString *)name
     268             : {
     269             :         BOOL                                            OK;
     270             :         BOOL                                            stop = NO;
     271             :         
     272             :         OK = [self verifyPList:plist
     273             :                                          named:name
     274             :                            subProperty:plist
     275             :                  againstSchemaType:_schema
     276             :                                         atPath:BackLinkRoot()
     277             :                                  tentative:NO
     278             :                                          error:NULL
     279             :                                           stop:&stop];
     280             :         
     281             :         return OK;
     282             : }
     283             : 
     284             : 
     285             : + (NSString *)descriptionForKeyPath:(NSArray *)keyPath
     286             : {
     287             :         NSMutableString                         *result = nil;
     288             :         NSEnumerator                            *componentEnum = nil;
     289             :         id                                                      component = nil;
     290             :         BOOL                                            first = YES;
     291             :         
     292             :         result = [NSMutableString string];
     293             :         
     294             :         for (componentEnum = [keyPath objectEnumerator]; (component = [componentEnum nextObject]); )
     295             :         {
     296             :                 if ([component isKindOfClass:[NSNumber class]])
     297             :                 {
     298             :                         [result appendFormat:@"[%@]", component];
     299             :                 }
     300             :                 else if ([component isKindOfClass:[NSString class]])
     301             :                 {
     302             :                         if (!first)  [result appendString:@"."];
     303             :                         [result appendString:component];
     304             :                 }
     305             :                 else  return nil;
     306             :                 first = NO;
     307             :         }
     308             :         
     309             :         if (first)
     310             :         {
     311             :                 // Empty path
     312             :                 return @"root";
     313             :         }
     314             :         
     315             :         return result;
     316             : }
     317             : 
     318             : @end
     319             : 
     320             : 
     321             : @implementation OOPListSchemaVerifier (OOPrivate)
     322             : 
     323             : - (BOOL)delegateVerifierWithPropertyList:(id)rootPList
     324             :                                                                    named:(NSString *)name
     325             :                                                         testProperty:(id)subPList
     326             :                                                                   atPath:(BackLinkChain)keyPath
     327             :                                                          againstType:(NSString *)typeKey
     328             :                                                                    error:(NSError **)outError
     329             : {
     330             :         BOOL                                    result;
     331             :         NSError                                 *error = nil;
     332             :         
     333             :         if ([_delegate respondsToSelector:@selector(verifier:withPropertyList:named:testProperty:atPath:againstType:error:)])
     334             :         {
     335             :                 @try
     336             :                 {
     337             :                         result = [_delegate verifier:self
     338             :                                                 withPropertyList:rootPList
     339             :                                                                    named:name
     340             :                                                         testProperty:subPList
     341             :                                                                   atPath:KeyPathToArray(keyPath)
     342             :                                                          againstType:typeKey
     343             :                                                                    error:&error];
     344             :                 }
     345             :                 @catch (NSException *exception)
     346             :                 {
     347             :                         OOLog(@"plistVerifier.delegateException", @"Property list schema verifier: delegate threw exception (%@) in -verifier:withPropertyList:named:testProperty:atPath:againstType: for type \"%@\" at %@ in %@ -- treating as failure.", [exception name], typeKey,KeyPathToString(keyPath), name);
     348             :                         result = NO;
     349             :                         error = nil;
     350             :                 }
     351             :                 
     352             :                 if (outError != NULL)
     353             :                 {
     354             :                         if (!result || error != nil)
     355             :                         {
     356             :                                 // Note: Generates an error if delegate returned NO (meaning stop) or if delegate produced an error but did not request a stop.
     357             :                                 *outError = ErrorWithProperty(kPListDelegatedTypeError, &keyPath, NSUnderlyingErrorKey, error, @"Value at %@ does not match delegated type \"%@\".", KeyPathToString(keyPath), typeKey);
     358             :                         }
     359             :                         else *outError = nil;
     360             :                 }
     361             :         }
     362             :         else
     363             :         {
     364             :                 if (!_badDelegateWarning)
     365             :                 {
     366             :                         OOLog(@"plistVerifier.badDelegate", @"%@", @"Property list schema verifier: delegate does not handle delegated types.");
     367             :                         _badDelegateWarning = YES;
     368             :                 }
     369             :                 result = YES;
     370             :         }
     371             :         
     372             :         return result;
     373             : }
     374             : 
     375             : 
     376             : - (BOOL)delegateVerifierWithPropertyList:(id)rootPList
     377             :                                                                    named:(NSString *)name
     378             :                                            failedForProperty:(id)subPList
     379             :                                                            withError:(NSError *)error
     380             :                                                         expectedType:(NSDictionary *)localSchema
     381             : {
     382             :         BOOL                                    result;
     383             :         
     384             :         if ([_delegate respondsToSelector:@selector(verifier:withPropertyList:named:failedForProperty:withError:expectedType:)])
     385             :         {
     386             :                 @try
     387             :                 {
     388             :                         result = [_delegate verifier:self
     389             :                                                 withPropertyList:rootPList
     390             :                                                                    named:name
     391             :                                            failedForProperty:subPList
     392             :                                                            withError:error
     393             :                                                         expectedType:localSchema];
     394             :                 }
     395             :                 @catch (NSException *exception)
     396             :                 {
     397             :                         OOLog(@"plistVerifier.delegateException", @"Property list schema verifier: delegate threw exception (%@) in -verifier:withPropertyList:named:failedForProperty:atPath:expectedType: at %@ in %@ -- stopping.", [exception name], [error plistKeyPathDescription], name);
     398             :                         result = NO;
     399             :                 }
     400             :         }
     401             :         else
     402             :         {
     403             :                 OOLog(@"plistVerifier.failed", @"Verification of property list \"%@\" failed at %@: %@", name, [error plistKeyPathDescription], [error localizedFailureReason]);
     404             :                 result = NO;
     405             :         }
     406             :         return result;
     407             : }
     408             : 
     409             : 
     410             : - (BOOL)verifyPList:(id)rootPList
     411             :                           named:(NSString *)name
     412             :                 subProperty:(id)subProperty
     413             :   againstSchemaType:(id)subSchema
     414             :                          atPath:(BackLinkChain)keyPath
     415             :                   tentative:(BOOL)tentative
     416             :                           error:(NSError **)outError
     417             :                            stop:(BOOL *)outStop
     418             : {
     419             :         SchemaType                              type = kTypeUnknown;
     420             :         NSError                                 *error = nil;
     421             :         NSDictionary                    *resolvedSpecifier = nil;
     422             :         NSAutoreleasePool               *pool = nil;
     423             :         
     424             :         assert(outStop != NULL);
     425             :         
     426             :         pool = [[NSAutoreleasePool alloc] init];
     427             :         
     428             :         DebugDumpPushIndent();
     429             :         
     430             :         @try
     431             :         {
     432             :                 DebugDumpIndent();
     433             :                 
     434             :                 resolvedSpecifier = [self resolveSchemaType:subSchema atPath:keyPath error:&error];
     435             :                 if (resolvedSpecifier != nil)  type = StringToSchemaType([resolvedSpecifier objectForKey:@"type"], &error);
     436             :                 
     437           0 :                 #define VERIFY_CASE(T) case kType##T: error = Verify_##T(self, subProperty, resolvedSpecifier, rootPList, name, keyPath, tentative, outStop); break;
     438             :                 
     439             :                 switch (type)
     440             :                 {
     441             :                         VERIFY_CASE(String);
     442             :                         VERIFY_CASE(Array);
     443             :                         VERIFY_CASE(Dictionary);
     444             :                         VERIFY_CASE(Integer);
     445             :                         VERIFY_CASE(PositiveInteger);
     446             :                         VERIFY_CASE(Float);
     447             :                         VERIFY_CASE(PositiveFloat);
     448             :                         VERIFY_CASE(OneOf);
     449             :                         VERIFY_CASE(Enumeration);
     450             :                         VERIFY_CASE(Boolean);
     451             :                         VERIFY_CASE(FuzzyBoolean);
     452             :                         VERIFY_CASE(Vector);
     453             :                         VERIFY_CASE(Quaternion);
     454             :                         VERIFY_CASE(DelegatedType);
     455             :                         
     456             :                         case kTypeUnknown:
     457             :                                 // resolveSchemaType:... or StringToSchemaType() should have provided an error.
     458             :                                 *outStop = YES;
     459             :                 }
     460             :         }
     461             :         @catch (NSException *exception)
     462             :         {
     463             :                 error = Error(kPListErrorInternal, (BackLinkChain *)&keyPath, @"Uncaught exception %@: %@ in plist verifier for \"%@\" at %@.", [exception name], [exception reason], name, KeyPathToString(keyPath));
     464             :         }
     465             :         
     466             :         DebugDumpPopIndent();
     467             :         
     468             :         if (error != nil)
     469             :         {
     470             :                 if (!tentative && !IsFailureAlreadyReportedError(error))
     471             :                 {
     472             :                         *outStop = ![self delegateVerifierWithPropertyList:rootPList
     473             :                                                                                                                  named:name
     474             :                                                                                          failedForProperty:subProperty
     475             :                                                                                                          withError:error
     476             :                                                                                                   expectedType:subSchema];
     477             :                 }
     478             :                 else if (tentative)  *outStop = YES;
     479             :         }
     480             :         
     481             :         if (outError != NULL && error != nil)
     482             :         {
     483             :                 *outError = [error retain];
     484             :                 [pool release];
     485             :                 [error autorelease];
     486             :         }
     487             :         else
     488             :         {
     489             :                 [pool release];
     490             :         }
     491             :         
     492             :         return error == nil;
     493             : }
     494             : 
     495             : 
     496             : - (NSDictionary *)resolveSchemaType:(id)specifier
     497             :                                                          atPath:(BackLinkChain)keyPath
     498             :                                                           error:(NSError **)outError
     499             : {
     500             :         id                                              typeVal = nil;
     501             :         NSString                                *complaint = nil;
     502             :         
     503             :         assert(outError != NULL);
     504             :         
     505             :         if (![specifier isKindOfClass:[NSString class]] && ![specifier isKindOfClass:[NSDictionary class]])  goto BAD_TYPE;
     506             :         
     507             :         for (;;)
     508             :         {
     509             :                 if ([specifier isKindOfClass:[NSString class]])  specifier = [NSDictionary dictionaryWithObject:specifier forKey:@"type"];
     510             :                 typeVal = [(NSDictionary *)specifier objectForKey:@"type"];
     511             :                 
     512             :                 if ([typeVal isKindOfClass:[NSString class]])
     513             :                 {
     514             :                         if ([typeVal hasPrefix:@"$"])
     515             :                         {
     516             :                                 // Macro reference; look it up in $definitions
     517             :                                 specifier = [_definitions objectForKey:typeVal];
     518             :                                 if (specifier == nil)
     519             :                                 {
     520             :                                         *outError = ErrorWithProperty(kPListErrorSchemaUndefiniedMacroReference, &keyPath, kUndefinedMacroErrorKey, typeVal, @"Bad schema: reference to undefined macro \"%@\".", StringForErrorReport(typeVal));
     521             :                                         return nil;
     522             :                                 }
     523             :                         }
     524             :                         else
     525             :                         {
     526             :                                 // Non-macro string
     527             :                                 return specifier;
     528             :                         }
     529             :                 }
     530             :                 else if ([typeVal isKindOfClass:[NSDictionary class]])
     531             :                 {
     532             :                         specifier = typeVal;    
     533             :                 }
     534             :                 else
     535             :                 {
     536             :                         goto BAD_TYPE;
     537             :                 }
     538             :         }
     539             :         
     540             : BAD_TYPE:
     541             :         // Error: bad type
     542             :         if (typeVal == nil)  complaint = @"no type specified";
     543             :         else  complaint = @"not string or dictionary";
     544             :                                 
     545             :         *outError = Error(kPListErrorSchemaBadTypeSpecifier, &keyPath, @"Bad schema: invalid type specifier for path %@ (%@).", KeyPathToString(keyPath), complaint);
     546             :         return nil;
     547             : }
     548             : 
     549             : @end
     550             : 
     551             : 
     552           0 : static SchemaType StringToSchemaType(NSString *string, NSError **outError)
     553             : {
     554             :         static NSDictionary                     *typeMap = nil;
     555             :         SchemaType                                      result;
     556             :         
     557             :         if (typeMap == nil)
     558             :         {
     559             :                 typeMap =
     560             :                         [[NSDictionary dictionaryWithObjectsAndKeys:
     561             :                                 [NSNumber numberWithUnsignedInt:kTypeString],                   @"string",
     562             :                                 [NSNumber numberWithUnsignedInt:kTypeArray],                    @"array",
     563             :                                 [NSNumber numberWithUnsignedInt:kTypeDictionary],               @"dictionary",
     564             :                                 [NSNumber numberWithUnsignedInt:kTypeInteger],                  @"integer",
     565             :                                 [NSNumber numberWithUnsignedInt:kTypePositiveInteger],  @"positiveInteger",
     566             :                                 [NSNumber numberWithUnsignedInt:kTypeFloat],                    @"float",
     567             :                                 [NSNumber numberWithUnsignedInt:kTypePositiveFloat],    @"positiveFloat",
     568             :                                 [NSNumber numberWithUnsignedInt:kTypeOneOf],                    @"oneOf",
     569             :                                 [NSNumber numberWithUnsignedInt:kTypeEnumeration],              @"enumeration",
     570             :                                 [NSNumber numberWithUnsignedInt:kTypeBoolean],                  @"boolean",
     571             :                                 [NSNumber numberWithUnsignedInt:kTypeFuzzyBoolean],             @"fuzzyBoolean",
     572             :                                 [NSNumber numberWithUnsignedInt:kTypeVector],                   @"vector",
     573             :                                 [NSNumber numberWithUnsignedInt:kTypeQuaternion],               @"quaternion",
     574             :                                 [NSNumber numberWithUnsignedInt:kTypeDelegatedType],    @"delegatedType",
     575             :                                 nil
     576             :                          ] retain];
     577             :         }
     578             :         
     579             :         result = [[typeMap objectForKey:string] unsignedIntValue];
     580             :         if (result == kTypeUnknown && outError != NULL)
     581             :         {
     582             :                 if ([string hasPrefix:@"$"])
     583             :                 {
     584             :                         *outError = ErrorWithProperty(kPListErrorSchemaUnknownType, NULL, kUnknownTypeErrorKey, string, @"Bad schema: unresolved macro reference \"%@\".", string);
     585             :                 }
     586             :                 else
     587             :                 {
     588             :                         *outError = ErrorWithProperty(kPListErrorSchemaUnknownType, NULL, kUnknownTypeErrorKey, string, @"Bad schema: unknown type \"%@\".", string);
     589             :                 }
     590             :         }
     591             :         
     592             :         return result;
     593             : }
     594             : 
     595             : 
     596           0 : static NSString *ApplyStringFilter(NSString *string, id filterSpec, BackLinkChain keyPath, NSError **outError)
     597             : {
     598             :         NSEnumerator                    *filterEnum = nil;
     599             :         id                                              filter = nil;
     600             :         NSRange                                 range;
     601             :         
     602             :         assert(outError != NULL);
     603             :         
     604             :         if (filterSpec == nil)  return string;
     605             :         
     606             :         if ([filterSpec isKindOfClass:[NSString class]])
     607             :         {
     608             :                 filterSpec = [NSArray arrayWithObject:filterSpec];
     609             :         }
     610             :         if ([filterSpec isKindOfClass:[NSArray class]])
     611             :         {
     612             :                 for (filterEnum = [filterSpec objectEnumerator]; (filter = [filterEnum nextObject]); )
     613             :                 {
     614             :                         if ([filter isKindOfClass:[NSString class]])
     615             :                         {
     616             :                                 if ([filter isEqual:@"lowerCase"])  string = [string lowercaseString];
     617             :                                 else if ([filter isEqual:@"upperCase"])  string = [string uppercaseString];
     618             :                                 else if ([filter isEqual:@"capitalized"])  string = [string capitalizedString];
     619             :                                 else if ([filter hasPrefix:@"truncFront:"])
     620             :                                 {
     621             :                                         string = [string substringToIndex:[[filter substringFromIndex:11] intValue]];
     622             :                                 }
     623             :                                 else if ([filter hasPrefix:@"truncBack:"])
     624             :                                 {
     625             :                                         string = [string substringToIndex:[[filter substringFromIndex:10] intValue]];
     626             :                                 }
     627             :                                 else if ([filter hasPrefix:@"subStringTo:"])
     628             :                                 {
     629             :                                         range = [string rangeOfString:[filter substringFromIndex:12]];
     630             :                                         if (range.location != NSNotFound)
     631             :                                         {
     632             :                                                 string = [string substringToIndex:range.location];
     633             :                                         }
     634             :                                 }
     635             :                                 else if ([filter hasPrefix:@"subStringFrom:"])
     636             :                                 {
     637             :                                         range = [string rangeOfString:[filter substringFromIndex:14]];
     638             :                                         if (range.location != NSNotFound)
     639             :                                         {
     640             :                                                 string = [string substringFromIndex:range.location + range.length];
     641             :                                         }
     642             :                                 }
     643             :                                 else if ([filter hasPrefix:@"subStringToInclusive:"])
     644             :                                 {
     645             :                                         range = [string rangeOfString:[filter substringFromIndex:21]];
     646             :                                         if (range.location != NSNotFound)
     647             :                                         {
     648             :                                                 string = [string substringToIndex:range.location + range.length];
     649             :                                         }
     650             :                                 }
     651             :                                 else if ([filter hasPrefix:@"subStringFromInclusive:"])
     652             :                                 {
     653             :                                         range = [string rangeOfString:[filter substringFromIndex:23]];
     654             :                                         if (range.location != NSNotFound)
     655             :                                         {
     656             :                                                 string = [string substringFromIndex:range.location];
     657             :                                         }
     658             :                                 }
     659             :                                 else
     660             :                                 {
     661             :                                         *outError = ErrorWithProperty(kPListErrorSchemaUnknownFilter, &keyPath, kUnnownFilterErrorKey, filter, @"Bad schema: unknown string filter specifier \"%@\".", filter);
     662             :                                 }
     663             :                         }
     664             :                         else
     665             :                         {
     666             :                                 *outError = Error(kPListErrorSchemaUnknownFilter, &keyPath, @"Bad schema: filter specifier is not a string.");
     667             :                         }
     668             :                 }
     669             :         }
     670             :         else
     671             :         {
     672             :                 *outError = Error(kPListErrorSchemaUnknownFilter, &keyPath, @"Bad schema: \"filter\" must be a string or an array.");
     673             :         }
     674             :         
     675             :         return string;
     676             : }
     677             : 
     678             : 
     679           0 : static BOOL ApplyStringTest(NSString *string, id test, SEL testSelector, NSString *testDescription, BackLinkChain keyPath, NSError **outError)
     680             : {
     681             :         BOOL                                    (*testIMP)(id, SEL, NSString *);
     682             :         NSEnumerator                    *testEnum = nil;
     683             :         id                                              subTest = nil;
     684             :         
     685             :         assert(outError != NULL);
     686             :         
     687             :         if (test == nil)  return YES;
     688             :         
     689             :         testIMP = (BOOL(*)(id, SEL, NSString *))[string methodForSelector:testSelector];
     690             :         if (testIMP == NULL)
     691             :         {
     692             :                 *outError = Error(kPListErrorInternal, &keyPath, @"OOPListSchemaVerifier internal error: NSString does not respond to test selector %@.", NSStringFromSelector(testSelector));
     693             :                 return NO;
     694             :         }
     695             :         
     696             :         if ([test isKindOfClass:[NSString class]])
     697             :         {
     698             :                 test = [NSArray arrayWithObject:test];
     699             :         }
     700             :         
     701             :         if ([test isKindOfClass:[NSArray class]])
     702             :         {
     703             :                 for (testEnum = [test objectEnumerator]; (subTest = [testEnum nextObject]); )
     704             :                 {
     705             :                         if ([subTest isKindOfClass:[NSString class]])
     706             :                         {
     707             :                                 if (testIMP(string, testSelector, subTest))  return YES;
     708             :                         }
     709             :                         else
     710             :                         {
     711             :                                 *outError = Error(kPListErrorSchemaBadComparator, &keyPath, @"Bad schema: required %@ is not a string.", testDescription);
     712             :                                 return NO;
     713             :                         }
     714             :                 }
     715             :         }
     716             :         else
     717             :         {
     718             :                 *outError = Error(kPListErrorSchemaBadComparator, &keyPath, @"Bad schema: %@ requirement specification is not a string or array.", testDescription);
     719             :         }
     720             :         return NO;
     721             : }
     722             : 
     723             : 
     724           0 : static NSArray *KeyPathToArray(BackLinkChain keyPath)
     725             : {
     726             :         NSMutableArray                  *result = nil;
     727             :         BackLinkChain                   *curr = NULL;
     728             :         
     729             :         result = [NSMutableArray array];
     730             :         for (curr = &keyPath; curr != NULL; curr = curr->link)
     731             :         {
     732             :                 if (curr->element != nil)  [result insertObject:curr->element atIndex:0];
     733             :         }
     734             :         
     735             :         return result;
     736             : }
     737             : 
     738             : 
     739           0 : static NSString *KeyPathToString(BackLinkChain keyPath)
     740             : {
     741             :         return [OOPListSchemaVerifier descriptionForKeyPath:KeyPathToArray(keyPath)];
     742             : }
     743             : 
     744             : 
     745           0 : static NSString *StringForErrorReport(NSString *string)
     746             : {
     747             :         id                                              result = nil;
     748             :         
     749             :         if (kMaximumLengthForStringInErrorMessage < [string length])
     750             :         {
     751             :                 string = [string substringToIndex:kMaximumLengthForStringInErrorMessage];
     752             :         }
     753             :         result = [NSMutableString stringWithString:string];
     754             :         [result replaceOccurrencesOfString:@"\t" withString:@"    " options:0 range:NSMakeRange(0, [string length])];
     755             :         [result replaceOccurrencesOfString:@"\r\n" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])];
     756             :         [result replaceOccurrencesOfString:@"\n" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])];
     757             :         [result replaceOccurrencesOfString:@"\r" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])];
     758             :         
     759             :         if (kMaximumLengthForStringInErrorMessage < [result length])
     760             :         {
     761             :                 result = [result substringToIndex:kMaximumLengthForStringInErrorMessage - 3];
     762             :                 result = [result stringByAppendingString:@"..."];
     763             :         }
     764             :         
     765             :         return result;
     766             : }
     767             : 
     768             : 
     769           0 : static NSString *ArrayForErrorReport(NSArray *array)
     770             : {
     771             :         NSString                                *result = nil;
     772             :         NSString                                *string = nil;
     773             :         NSUInteger                              i, count;
     774             :         NSAutoreleasePool               *pool = nil;
     775             :         
     776             :         count = [array count];
     777             :         if (count == 0)  return @"( )";
     778             :         
     779             :         pool = [[NSAutoreleasePool alloc] init];
     780             :         
     781             :         result = [NSString stringWithFormat:@"(%@", [array objectAtIndex:0]];
     782             :         
     783             :         for (i = 1; i != count; ++i)
     784             :         {
     785             :                 string = [result stringByAppendingFormat:@", %@", [array objectAtIndex:i]];
     786             :                 if (kMaximumLengthForStringInErrorMessage < [string length])
     787             :                 {
     788             :                         result = [result stringByAppendingString:@", ..."];
     789             :                         break;
     790             :                 }
     791             :                 result = string;
     792             :         }
     793             :         
     794             :         result = [result stringByAppendingString:@")"];
     795             :         
     796             :         [result retain];
     797             :         [pool release];
     798             :         return [result autorelease];
     799             : }
     800             : 
     801             : 
     802           0 : static NSString *SetForErrorReport(NSSet *set)
     803             : {
     804             :         return ArrayForErrorReport([[set allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
     805             : }
     806             : 
     807             : 
     808           0 : static NSString *StringOrArrayForErrorReport(id value, NSString *arrayPrefix)
     809             : {
     810             :         if ([value isKindOfClass:[NSString class]])
     811             :         {
     812             :                 return [NSString stringWithFormat:@"\"%@\"", StringForErrorReport(value)];
     813             :         }
     814             :         
     815             :         if (arrayPrefix == nil)  arrayPrefix = @"";
     816             :         if ([value isKindOfClass:[NSArray class]])
     817             :         {
     818             :                 return [arrayPrefix stringByAppendingString:ArrayForErrorReport(value)];
     819             :         }
     820             :         if ([value isKindOfClass:[NSSet class]])
     821             :         {
     822             :                 return [arrayPrefix stringByAppendingString:SetForErrorReport(value)];
     823             :         }
     824             :         if (value == nil)  return @"(null)";
     825             :         return @"<?>";
     826             : }
     827             : 
     828             : 
     829             : // Specific type verifiers
     830             : 
     831           0 : #define REQUIRE_TYPE(CLASSNAME, NAMESTRING)      do { \
     832             :                 if (![value isKindOfClass:[CLASSNAME class]]) \
     833             :                 { \
     834             :                         return ErrorTypeMismatch([CLASSNAME class], NAMESTRING, value, keyPath); \
     835             :                 } \
     836             :         } while (0)
     837             : 
     838           0 : static NSError *Verify_String(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
     839             : {
     840             :         NSString                        *filteredString = nil;
     841             :         id                                      testValue = nil;
     842             :         NSUInteger                      length;
     843             :         NSUInteger                      lengthConstraint;
     844             :         NSError                         *error = nil;
     845             :         
     846             :         REQUIRE_TYPE(NSString, @"string");
     847             :         
     848             :         DebugDump(@"* string: \"%@\"", StringForErrorReport(value));
     849             :         
     850             :         // Apply filters
     851             :         filteredString = ApplyStringFilter(value, [params objectForKey:@"filter"], keyPath, &error);
     852             :         if (filteredString == nil)  return error;
     853             :         
     854             :         // Apply substring requirements
     855             :         testValue = [params objectForKey:@"requiredPrefix"];
     856             :         if (testValue != nil)
     857             :         {
     858             :                 if (!ApplyStringTest(filteredString, testValue, @selector(hasPrefix:), @"prefix", keyPath, &error))
     859             :                 {
     860             :                         if (error == nil)  error = ErrorWithProperty(kPListErrorStringPrefixMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"prefix", StringOrArrayForErrorReport(testValue, @"in "));
     861             :                         return error;
     862             :                 }
     863             :         }
     864             :         
     865             :         testValue = [params objectForKey:@"requiredSuffix"];
     866             :         if (testValue != nil)
     867             :         {
     868             :                 if (!ApplyStringTest(filteredString, testValue, @selector(hasSuffix:), @"suffix", keyPath, &error))
     869             :                 {
     870             :                         if (error == nil)  error = ErrorWithProperty(kPListErrorStringSuffixMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"suffix", StringOrArrayForErrorReport(testValue, @"in "));
     871             :                         return error;
     872             :                 }
     873             :         }
     874             :         
     875             :         testValue = [params objectForKey:@"requiredSubString"];
     876             :         if (testValue != nil)
     877             :         {
     878             :                 if (!ApplyStringTest(filteredString, testValue, @selector(ooPListVerifierHasSubString:), @"substring", keyPath, &error))
     879             :                 {
     880             :                         if (error == nil)  error = ErrorWithProperty(kPListErrorStringSubstringMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"substring", StringOrArrayForErrorReport(testValue, @"in "));
     881             :                         return error;
     882             :                 }
     883             :         }
     884             :         
     885             :         // Apply length bounds.
     886             :         length = [filteredString length];
     887             :         lengthConstraint = [params oo_unsignedIntegerForKey:@"minLength"];
     888             :         if (length < lengthConstraint)
     889             :         {
     890             :                 return  Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"String \"%@\" is too short (%u bytes, minimum is %u).", StringForErrorReport(filteredString), length, lengthConstraint);
     891             :         }
     892             :         
     893             :         lengthConstraint = [params oo_unsignedIntegerForKey:@"maxLength" defaultValue:NSUIntegerMax];
     894             :         if (lengthConstraint < length)
     895             :         {
     896             :                 return  Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"String \"%@\" is too long (%u bytes, maximum is %u).", StringForErrorReport(filteredString), length, lengthConstraint);
     897             :         }
     898             :         
     899             :         // All tests passed.
     900             :         return nil;
     901             : }
     902             : 
     903             : 
     904           0 : static NSError *Verify_Array(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
     905             : {
     906             :         id                                              valueType = nil;
     907             :         BOOL                                    OK = YES, stop = NO;
     908             :         NSUInteger                              i, count;
     909             :         id                                              subProperty = nil;
     910             :         NSUInteger                              constraint;
     911             :         
     912             :         REQUIRE_TYPE(NSArray, @"array");
     913             :         
     914             :         DebugDump(@"%@", @"* array");
     915             :         
     916             :         // Apply count bounds.
     917             :         count = [value count];
     918             :         constraint = [params oo_unsignedIntegerForKey:@"minCount" defaultValue:0];
     919             :         if (count < constraint)
     920             :         {
     921             :                 return  Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Array has too few members (%u, minimum is %u).", count, constraint);
     922             :         }
     923             :         
     924             :         constraint = [params oo_unsignedIntegerForKey:@"maxCount" defaultValue:NSUIntegerMax];
     925             :         if (constraint < count)
     926             :         {
     927             :                 return  Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Array has too many members (%u, maximum is %u).", count, constraint);
     928             :         }
     929             :         
     930             :         // Test member objects.
     931             :         valueType = [params objectForKey:@"valueType"];
     932             :         if (valueType != nil)
     933             :         {
     934             :                 for (i = 0; i != count; ++i)
     935             :                 {
     936             :                         subProperty = [value objectAtIndex:i];
     937             :                         
     938             :                         if (![verifier verifyPList:rootPList
     939             :                                                                  named:name
     940             :                                                    subProperty:subProperty
     941             :                                          againstSchemaType:valueType
     942             :                                                                 atPath:BackLinkIndex(&keyPath, i)
     943             :                                                          tentative:tentative
     944             :                                                                  error:NULL
     945             :                                                                   stop:&stop])
     946             :                         {
     947             :                                 OK = NO;
     948             :                         }
     949             :                         
     950             :                         if ((stop && !tentative) || (tentative && !OK))  break;
     951             :                 }
     952             :         }
     953             :         
     954             :         *outStop = stop && !tentative;
     955             :         
     956             :         if (!OK)  return ErrorFailureAlreadyReported();
     957             :         else  return nil;
     958             : }
     959             : 
     960             : 
     961           0 : static NSError *Verify_Dictionary(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
     962             : {
     963             :         NSDictionary                    *schema = nil;
     964             :         id                                              valueType = nil,
     965             :                                                         typeSpec = nil;
     966             :         NSEnumerator                    *keyEnum = nil;
     967             :         NSString                                *key = nil;
     968             :         id                                              subProperty = nil;
     969             :         BOOL                                    OK = YES, stop = NO, prematureExit = NO;
     970             :         BOOL                                    allowOthers;
     971             :         NSMutableSet                    *requiredKeys = nil;
     972             :         NSArray                                 *requiredKeyList = nil;
     973             :         NSUInteger                              count, constraint;
     974             :         
     975             :         REQUIRE_TYPE(NSDictionary, @"dictionary");
     976             :         
     977             :         DebugDump(@"%@", @"* dictionary");
     978             :         
     979             :         // Apply count bounds.
     980             :         count = [value count];
     981             :         constraint = [params oo_unsignedIntegerForKey:@"minCount" defaultValue:0];
     982             :         if (count < constraint)
     983             :         {
     984             :                 return  Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Dictionary has too few pairs (%u, minimum is %u).", count, constraint);
     985             :         }
     986             :         constraint = [params oo_unsignedIntegerForKey:@"maxCount" defaultValue:NSUIntegerMax];
     987             :         if (constraint < count)
     988             :         {
     989             :                 return  Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Dictionary has too manu pairs (%u, maximum is %u).", count, constraint);
     990             :         }
     991             :         
     992             :         // Get schema.
     993             :         schema = [params oo_dictionaryForKey:@"schema"];
     994             :         valueType = [params objectForKey:@"valueType"];
     995             :         allowOthers = [params oo_boolForKey:@"allowOthers" defaultValue:YES];
     996             :         requiredKeyList = [params oo_arrayForKey:@"requiredKeys"];
     997             :         
     998             :         // If these conditions are met, all members must pass:
     999             :         if (schema == nil && valueType == nil && requiredKeyList == nil && allowOthers)  return nil;
    1000             :         
    1001             :         if (requiredKeyList != nil)
    1002             :         {
    1003             :                 requiredKeys = [NSMutableSet setWithArray:requiredKeyList];
    1004             :         }
    1005             :         
    1006             :         DebugDumpIndent();
    1007             :         
    1008             :         // Test member objects.
    1009             :         for (keyEnum = [value keyEnumerator]; (key = [keyEnum nextObject]) && !stop; )
    1010             :         {
    1011             :                 subProperty = [(NSDictionary *)value objectForKey:key];
    1012             :                 typeSpec = [schema objectForKey:key];
    1013             :                 if (typeSpec == nil)  typeSpec = valueType;
    1014             :                 
    1015             :                 DebugDump(@"- \"%@\"", key);
    1016             :                 DebugDumpIndent();
    1017             :                 
    1018             :                 if (typeSpec != nil)
    1019             :                 {
    1020             :                         if (![verifier verifyPList:rootPList
    1021             :                                                                  named:name
    1022             :                                                    subProperty:subProperty
    1023             :                                          againstSchemaType:typeSpec
    1024             :                                                                 atPath:BackLink(&keyPath, key)
    1025             :                                                          tentative:tentative
    1026             :                                                                  error:NULL
    1027             :                                                                   stop:&stop])
    1028             :                         {
    1029             :                                 OK = NO;
    1030             :                         }
    1031             :                 }
    1032             :                 else if (!allowOthers && ![requiredKeys containsObject:key] && [schema objectForKey:key] == nil)
    1033             :                 {
    1034             :                         // Report error now rather than returning it, since there may be several unknown keys.
    1035             :                         if (!tentative)
    1036             :                         {
    1037             :                                 NSError *error = ErrorWithProperty(kPListErrorDictionaryUnknownKey, &keyPath, kUnknownKeyErrorKey, key, @"Unpermitted key \"%@\" in dictionary.", StringForErrorReport(key));
    1038             :                                 stop = ![verifier delegateVerifierWithPropertyList:rootPList
    1039             :                                                                                                                          named:name
    1040             :                                                                                                  failedForProperty:value
    1041             :                                                                                                                  withError:error
    1042             :                                                                                                           expectedType:params];
    1043             :                         }
    1044             :                         OK = NO;
    1045             :                 }
    1046             :                 
    1047             :                 DebugDumpOutdent();
    1048             :                 
    1049             :                 [requiredKeys removeObject:key];
    1050             :                 
    1051             :                 if ((stop && !tentative) || (tentative && !OK))
    1052             :                 {
    1053             :                         prematureExit = YES;
    1054             :                         break;
    1055             :                 }
    1056             :         }
    1057             :         
    1058             :         DebugDumpOutdent();
    1059             :         
    1060             :         // Check that all required keys were present.
    1061             :         if (!prematureExit && [requiredKeys count] != 0)
    1062             :         {
    1063             :                 return ErrorWithProperty(kPListErrorDictionaryMissingRequiredKeys, &keyPath, kMissingRequiredKeysErrorKey, requiredKeys, @"Required keys %@ missing from dictionary.", SetForErrorReport(requiredKeys));
    1064             :         }
    1065             :         
    1066             :         *outStop = stop && !tentative;
    1067             :         
    1068             :         if (!OK)  return ErrorFailureAlreadyReported();
    1069             :         else  return nil;
    1070             : }
    1071             : 
    1072             : 
    1073           0 : static NSError *Verify_Integer(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1074             : {
    1075             :         long long                               numericValue;
    1076             :         long long                               constraint;
    1077             :         
    1078             :         numericValue = OOLongLongFromObject(value, 0);
    1079             :         
    1080             :         DebugDump(@"* integer: %lli", numericValue);
    1081             :         
    1082             :         // Check basic parseability. If there's inequality here, the default value is being returned.
    1083             :         if (numericValue != OOLongLongFromObject(value, 1))
    1084             :         {
    1085             :                 return ErrorTypeMismatch([NSNumber class], @"integer", value, keyPath);
    1086             :         }
    1087             :         
    1088             :         // Check constraints.
    1089             :         constraint = [params oo_longLongForKey:@"minimum" defaultValue:LLONG_MIN];
    1090             :         if (numericValue < constraint)
    1091             :         {
    1092             :                 return  Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%lli, minimum is %lli).", numericValue, constraint);
    1093             :         }
    1094             :         
    1095             :         constraint = [params oo_longLongForKey:@"maximum" defaultValue:LLONG_MAX];
    1096             :         if (constraint < numericValue)
    1097             :         {
    1098             :                 return  Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%lli, maximum is %lli).", numericValue, constraint);
    1099             :         }
    1100             :         
    1101             :         return nil;
    1102             : }
    1103             : 
    1104             : 
    1105           0 : static NSError *Verify_PositiveInteger(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1106             : {
    1107             :         unsigned long long              numericValue;
    1108             :         unsigned long long              constraint;
    1109             :         
    1110             :         numericValue = OOUnsignedLongLongFromObject(value, 0);
    1111             :         
    1112             :         DebugDump(@"* positive integer: %llu", numericValue);
    1113             :         
    1114             :         // Check basic parseability. If there's inequality here, the default value is being returned.
    1115             :         if (numericValue != OOUnsignedLongLongFromObject(value, 1))
    1116             :         {
    1117             :                 return ErrorTypeMismatch([NSNumber class], @"positive integer", value, keyPath);
    1118             :         }
    1119             :         
    1120             :         // Check constraints.
    1121             :         constraint = [params oo_unsignedLongLongForKey:@"minimum" defaultValue:0];
    1122             :         if (numericValue < constraint)
    1123             :         {
    1124             :                 return  Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%llu, minimum is %llu).", numericValue, constraint);
    1125             :         }
    1126             :         
    1127             :         constraint = [params oo_unsignedLongLongForKey:@"maximum" defaultValue:ULLONG_MAX];
    1128             :         if (constraint < numericValue)
    1129             :         {
    1130             :                 return  Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%llu, maximum is %llu).", numericValue, constraint);
    1131             :         }
    1132             :         
    1133             :         return nil;
    1134             : }
    1135             : 
    1136             : 
    1137           0 : static NSError *Verify_Float(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1138             : {
    1139             :         double                                  numericValue;
    1140             :         double                                  constraint;
    1141             :         
    1142             :         numericValue = OODoubleFromObject(value, 0);
    1143             :         
    1144             :         DebugDump(@"* float: %g", numericValue);
    1145             :         
    1146             :         // Check basic parseability. If there's inequality here, the default value is being returned.
    1147             :         if (numericValue != OODoubleFromObject(value, 1))
    1148             :         {
    1149             :                 return ErrorTypeMismatch([NSNumber class], @"number", value, keyPath);
    1150             :         }
    1151             :         
    1152             :         // Check constraints.
    1153             :         constraint = [params oo_doubleForKey:@"minimum" defaultValue:-INFINITY];
    1154             :         if (numericValue < constraint)
    1155             :         {
    1156             :                 return  Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%g, minimum is %g).", numericValue, constraint);
    1157             :         }
    1158             :         
    1159             :         constraint = [params oo_doubleForKey:@"maximum" defaultValue:INFINITY];
    1160             :         if (constraint < numericValue)
    1161             :         {
    1162             :                 return  Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%g, maximum is %g).", numericValue, constraint);
    1163             :         }
    1164             :         
    1165             :         return nil;
    1166             : }
    1167             : 
    1168             : 
    1169           0 : static NSError *Verify_PositiveFloat(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1170             : {
    1171             :         double                                  numericValue;
    1172             :         double                                  constraint;
    1173             :         
    1174             :         numericValue = OODoubleFromObject(value, 0);
    1175             :         
    1176             :         DebugDump(@"* positive float: %g", numericValue);
    1177             :         
    1178             :         // Check basic parseability. If there's inequality here, the default value is being returned.
    1179             :         if (numericValue != OODoubleFromObject(value, 1))
    1180             :         {
    1181             :                 return ErrorTypeMismatch([NSNumber class], @"positive number", value, keyPath);
    1182             :         }
    1183             :         
    1184             :         if (numericValue < 0)
    1185             :         {
    1186             :                 return Error(kPListErrorNumberIsNegative, &keyPath, @"Expected non-negative number, found %g.", numericValue);
    1187             :         }
    1188             :         
    1189             :         // Check constraints.
    1190             :         constraint = [params oo_doubleForKey:@"minimum" defaultValue:0];
    1191             :         if (numericValue < constraint)
    1192             :         {
    1193             :                 return  Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%g, minimum is %g).", numericValue, constraint);
    1194             :         }
    1195             :         
    1196             :         constraint = [params oo_doubleForKey:@"maximum" defaultValue:INFINITY];
    1197             :         if (constraint < numericValue)
    1198             :         {
    1199             :                 return  Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%g, maximum is %g).", numericValue, constraint);
    1200             :         }
    1201             :         
    1202             :         return nil;
    1203             : }
    1204             : 
    1205             : 
    1206           0 : static NSError *Verify_OneOf(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1207             : {
    1208             :         NSArray                                 *options = nil;
    1209             :         BOOL                                    OK = NO, stop = NO;
    1210             :         NSEnumerator                    *optionEnum = nil;
    1211             :         id                                              option = nil;
    1212             :         NSError                                 *error;
    1213             :         NSMutableDictionary             *errors = nil;
    1214             :         
    1215             :         DebugDump(@"%@", @"* oneOf");
    1216             :         
    1217             :         options = [params oo_arrayForKey:@"options"];
    1218             :         if (options == nil)
    1219             :         {
    1220             :                 *outStop = YES;
    1221             :                 return Error(kPListErrorSchemaNoOneOfOptions, &keyPath, @"Bad schema: no options specified for oneOf type.");
    1222             :         }
    1223             :         
    1224             :         errors = [[NSMutableDictionary alloc] initWithCapacity:[options count]];
    1225             :         
    1226             :         for (optionEnum = [options objectEnumerator]; (option = [optionEnum nextObject]) ;)
    1227             :         {
    1228             :                 if ([verifier verifyPList:rootPList
    1229             :                                                         named:name
    1230             :                                           subProperty:value
    1231             :                                 againstSchemaType:option
    1232             :                                                    atPath:keyPath
    1233             :                                                 tentative:YES
    1234             :                                                         error:&error
    1235             :                                                          stop:&stop])
    1236             :                 {
    1237             :                         DebugDump(@"%@", @"> Match.");
    1238             :                         OK = YES;
    1239             :                         break;
    1240             :                 }
    1241             :                 [errors setObject:error forKey:option];
    1242             :         }
    1243             :         
    1244             :         if (!OK)
    1245             :         {
    1246             :                 DebugDump(@"%@", @"! No match.");
    1247             :                 return ErrorWithProperty(kPListErrorOneOfNoMatch, &keyPath, kErrorsByOptionErrorKey, [errors autorelease], @"No matching type rule could be found.");
    1248             :         }
    1249             :         
    1250             :         // Ignore stop in tentatives.
    1251             :         [errors release];
    1252             :         return nil;
    1253             : }
    1254             : 
    1255             : 
    1256           0 : static NSError *Verify_Enumeration(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1257             : {
    1258             :         NSArray                                 *values = nil;
    1259             :         NSString                                *filteredString = nil;
    1260             :         NSError                                 *error = nil;
    1261             :         
    1262             :         DebugDump(@"%@", @"* enumeration");
    1263             :         
    1264             :         REQUIRE_TYPE(NSString, @"string");
    1265             :         
    1266             :         values = [params oo_arrayForKey:@"values"];
    1267             :         DebugDump(@"  - \"%@\" in %@", StringForErrorReport(value), ArrayForErrorReport(values));
    1268             :         
    1269             :         if (values == nil)
    1270             :         {
    1271             :                 *outStop = YES;
    1272             :                 return Error(kPListErrorSchemaNoEnumerationValues, &keyPath, @"Bad schema: no options specified for oneOf type.");
    1273             :         }
    1274             :         
    1275             :         filteredString = ApplyStringFilter(value, [params objectForKey:@"filter"], keyPath, &error);
    1276             :         if (filteredString == nil)  return error;
    1277             :         
    1278             :         if ([values containsObject:filteredString])  return nil;
    1279             :         
    1280             :         return Error(kPListErrorEnumerationBadValue, &keyPath, @"Value \"%@\" not recognized, should be one of %@.", StringForErrorReport(value), ArrayForErrorReport(values));
    1281             : }
    1282             : 
    1283             : 
    1284           0 : static NSError *Verify_Boolean(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1285             : {
    1286             :         DebugDump(@"* boolean: %@", value);
    1287             :         
    1288             :         // Check basic parseability. If there's inequality here, the default value is being returned.
    1289             :         if (OOBooleanFromObject(value, 0) == OOBooleanFromObject(value, 1))  return nil;
    1290             :         else  return ErrorTypeMismatch([NSNumber class], @"boolean", value, keyPath);
    1291             : }
    1292             : 
    1293             : 
    1294           0 : static NSError *Verify_FuzzyBoolean(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1295             : {
    1296             :         DebugDump(@"* fuzzy boolean: %@", value);
    1297             :         
    1298             :         // Check basic parseability. If there's inequality here, the default value is being returned.
    1299             :         if (OODoubleFromObject(value, 0) == OODoubleFromObject(value, 1))  return nil;
    1300             :         else if (OOBooleanFromObject(value, 0) == OOBooleanFromObject(value, 1))  return nil;
    1301             :         else  return ErrorTypeMismatch([NSNumber class], @"fuzzy boolean", value, keyPath);
    1302             : }
    1303             : 
    1304             : 
    1305           0 : static NSError *Verify_Vector(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1306             : {
    1307             :         DebugDump(@"* vector: %@", value);
    1308             :         
    1309             :         // Check basic parseability. If there's inequality here, the default value is being returned.
    1310             :         if (vector_equal(OOVectorFromObject(value, kZeroVector), OOVectorFromObject(value, kBasisXVector)))  return nil;
    1311             :         else  return ErrorTypeMismatch(Nil, @"vector", value, keyPath);
    1312             : }
    1313             : 
    1314             : 
    1315           0 : static NSError *Verify_Quaternion(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1316             : {
    1317             :         DebugDump(@"* quaternion: %@", value);
    1318             :         
    1319             :         // Check basic parseability. If there's inequality here, the default value is being returned.
    1320             :         if (quaternion_equal(OOQuaternionFromObject(value, kZeroQuaternion), OOQuaternionFromObject(value, kIdentityQuaternion)))  return nil;
    1321             :         else  return ErrorTypeMismatch(Nil, @"quaternion", value, keyPath);
    1322             : }
    1323             : 
    1324             : 
    1325           0 : static NSError *Verify_DelegatedType(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
    1326             : {
    1327             :         id                                              baseType = nil;
    1328             :         NSString                                *key = nil;
    1329             :         BOOL                                    stop = NO;
    1330             :         NSError                                 *error = nil;
    1331             :         
    1332             :         DebugDump(@"* delegated type: %@", [params objectForKey:@"key"]);
    1333             :         
    1334             :         baseType = [params objectForKey:@"baseType"];
    1335             :         if (baseType != nil)
    1336             :         {
    1337             :                 if (![verifier verifyPList:rootPList
    1338             :                                                          named:name
    1339             :                                            subProperty:value
    1340             :                                  againstSchemaType:baseType
    1341             :                                                         atPath:keyPath
    1342             :                                                  tentative:tentative
    1343             :                                                          error:NULL
    1344             :                                                           stop:&stop])
    1345             :                 {
    1346             :                         *outStop = stop;
    1347             :                         return nil;
    1348             :                 }
    1349             :         }
    1350             :         
    1351             :         key = [params objectForKey:@"key"];
    1352             :         *outStop = ![verifier delegateVerifierWithPropertyList:rootPList
    1353             :                                                                                                          named:name
    1354             :                                                                                           testProperty:value
    1355             :                                                                                                         atPath:keyPath
    1356             :                                                                                            againstType:key
    1357             :                                                                                                          error:&error];
    1358             :         return error;
    1359             : }
    1360             : 
    1361             : 
    1362             : @implementation NSString (OOPListSchemaVerifierHelpers)
    1363             : 
    1364             : - (BOOL)ooPListVerifierHasSubString:(NSString *)string
    1365             : {
    1366             :         return [self rangeOfString:string].location != NSNotFound;
    1367             : }
    1368             : 
    1369             : @end
    1370             : 
    1371             : 
    1372             : @implementation NSError (OOPListSchemaVerifierConveniences)
    1373             : 
    1374             : - (NSArray *)plistKeyPath
    1375             : {
    1376             :         return [[self userInfo] oo_arrayForKey:kPListKeyPathErrorKey];
    1377             : }
    1378             : 
    1379             : 
    1380             : - (NSString *)plistKeyPathDescription
    1381             : {
    1382             :         return [OOPListSchemaVerifier descriptionForKeyPath:[self plistKeyPath]];
    1383             : }
    1384             : 
    1385             : 
    1386             : - (NSSet *)missingRequiredKeys
    1387             : {
    1388             :         return [[self userInfo] oo_setForKey:kMissingRequiredKeysErrorKey];
    1389             : }
    1390             : 
    1391             : 
    1392             : - (Class)expectedClass
    1393             : {
    1394             :         return [[self userInfo] objectForKey:kExpectedClassErrorKey];
    1395             : }
    1396             : 
    1397             : 
    1398             : - (NSString *)expectedClassName
    1399             : {
    1400             :         NSString *result = [[self userInfo] objectForKey:kExpectedClassNameErrorKey];
    1401             :         if (result == nil)  result = [[self expectedClass] description];
    1402             :         return result;
    1403             : }
    1404             : 
    1405             : @end
    1406             : 
    1407             : 
    1408           0 : static NSError *Error(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *format, ...)
    1409             : {
    1410             :         NSError                         *result = nil;
    1411             :         va_list                         args;
    1412             :         
    1413             :         va_start(args, format);
    1414             :         result = ErrorWithDictionaryAndArguments(errorCode, keyPath, nil, format, args);
    1415             :         va_end(args);
    1416             :         
    1417             :         return result;
    1418             : }
    1419             : 
    1420             : 
    1421           0 : static NSError *ErrorWithProperty(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *propKey, id propValue, NSString *format, ...)
    1422             : {
    1423             :         NSError                         *result = nil;
    1424             :         va_list                         args;
    1425             :         NSDictionary            *dict = nil;
    1426             :         
    1427             :         if (propKey != nil && propValue != nil)
    1428             :         {
    1429             :                 dict = [NSDictionary dictionaryWithObject:propValue forKey:propKey];
    1430             :         }
    1431             :         va_start(args, format);
    1432             :         result = ErrorWithDictionaryAndArguments(errorCode, keyPath, dict, format, args);
    1433             :         va_end(args);
    1434             :         
    1435             :         return result;
    1436             : }
    1437             : 
    1438             : 
    1439           0 : static NSError *ErrorWithDictionary(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, ...)
    1440             : {
    1441             :         NSError                         *result = nil;
    1442             :         va_list                         args;
    1443             :         
    1444             :         va_start(args, format);
    1445             :         result = ErrorWithDictionaryAndArguments(errorCode, keyPath, dict, format, args);
    1446             :         va_end(args);
    1447             :         
    1448             :         return result;
    1449             : }
    1450             : 
    1451             : 
    1452           0 : static NSError *ErrorWithDictionaryAndArguments(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, va_list arguments)
    1453             : {
    1454             :         NSString                        *message = nil;
    1455             :         NSMutableDictionary     *userInfo = nil;
    1456             :         
    1457             :         message = [[NSString alloc] initWithFormat:format arguments:arguments];
    1458             :         
    1459             :         userInfo = [NSMutableDictionary dictionaryWithDictionary:dict];
    1460             :         [userInfo setObject:message forKey:NSLocalizedFailureReasonErrorKey];
    1461             :         if (keyPath != NULL)
    1462             :         {
    1463             :                 [userInfo setObject:KeyPathToArray(*keyPath) forKey:kPListKeyPathErrorKey];
    1464             :         }
    1465             :         
    1466             :         [message release];
    1467             :         
    1468             :         return [NSError errorWithDomain:kOOPListSchemaVerifierErrorDomain code:errorCode userInfo:userInfo];
    1469             : }
    1470             : 
    1471             : 
    1472           0 : static NSError *ErrorTypeMismatch(Class expectedClass, NSString *expectedClassName, id actualObject, BackLinkChain keyPath)
    1473             : {
    1474             :         NSDictionary            *dict = nil;
    1475             :         NSString                        *className = nil;
    1476             :         
    1477             :         if (expectedClassName == nil)  expectedClassName = [expectedClass description];
    1478             :         
    1479             :         dict = [NSDictionary dictionaryWithObjectsAndKeys:
    1480             :                                 expectedClassName, kExpectedClassNameErrorKey,
    1481             :                                 expectedClass, kExpectedClassErrorKey,
    1482             :                                 nil];
    1483             :         
    1484             :         if (actualObject == nil)  className = @"nothing";
    1485             :         else if ([actualObject isKindOfClass:[NSString class]])  className = @"string";
    1486             :         else if ([actualObject isKindOfClass:[NSNumber class]])  className = @"number";
    1487             :         else if ([actualObject isKindOfClass:[NSArray class]])  className = @"array";
    1488             :         else if ([actualObject isKindOfClass:[NSDictionary class]])  className = @"dictionary";
    1489             :         else if ([actualObject isKindOfClass:[NSData class]])  className = @"data";
    1490             :         else if ([actualObject isKindOfClass:[NSDate class]])  className = @"date";
    1491             :         else  className = [[actualObject class] description];
    1492             :         
    1493             :         return ErrorWithDictionary(kPListErrorTypeMismatch, &keyPath, dict, @"Expected %@, found %@.", expectedClassName, className);
    1494             : }
    1495             : 
    1496             : 
    1497           0 : static NSError *ErrorFailureAlreadyReported(void)
    1498             : {
    1499             :         return [NSError errorWithDomain:kOOPListSchemaVerifierErrorDomain code:kPListErrorFailedAndErrorHasBeenReported userInfo:nil];
    1500             : }
    1501             : 
    1502             : 
    1503           0 : static BOOL IsFailureAlreadyReportedError(NSError *error)
    1504             : {
    1505             :         return [[error domain] isEqualToString:kOOPListSchemaVerifierErrorDomain] && [error code] == kPListErrorFailedAndErrorHasBeenReported;
    1506             : }
    1507             : 
    1508             : #endif  // OO_OXP_VERIFIER_ENABLED

Generated by: LCOV version 1.14