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