Oolite 1.91.0.7604-240417-a536cbe
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 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{
420 NSError *error = nil;
421 NSDictionary *resolvedSpecifier = nil;
422 NSAutoreleasePool *pool = nil;
423
424 assert(outStop != NULL);
425
426 pool = [[NSAutoreleasePool alloc] init];
427
429
430 @try
431 {
433
434 resolvedSpecifier = [self resolveSchemaType:subSchema atPath:keyPath error:&error];
435 if (resolvedSpecifier != nil) type = StringToSchemaType([resolvedSpecifier objectForKey:@"type"], &error);
436
437 #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
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
540BAD_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
552static 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
596static 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
679static 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
724static 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
739static NSString *KeyPathToString(BackLinkChain keyPath)
740{
741 return [OOPListSchemaVerifier descriptionForKeyPath:KeyPathToArray(keyPath)];
742}
743
744
745static 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
769static 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
802static NSString *SetForErrorReport(NSSet *set)
803{
804 return ArrayForErrorReport([[set allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
805}
806
807
808static 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#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
838static 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
904static 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
961static 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
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);
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
1048
1049 [requiredKeys removeObject:key];
1050
1051 if ((stop && !tentative) || (tentative && !OK))
1052 {
1053 prematureExit = YES;
1054 break;
1055 }
1056 }
1057
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
1073static 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
1105static 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
1137static 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
1169static 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
1206static 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
1256static 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
1284static 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
1294static 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
1305static 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
1315static 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
1325static 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
1408static 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
1421static 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
1439static 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
1452static 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
1472static 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
1497static NSError *ErrorFailureAlreadyReported(void)
1498{
1499 return [NSError errorWithDomain:kOOPListSchemaVerifierErrorDomain code:kPListErrorFailedAndErrorHasBeenReported userInfo:nil];
1500}
1501
1502
1503static BOOL IsFailureAlreadyReportedError(NSError *error)
1504{
1505 return [[error domain] isEqualToString:kOOPListSchemaVerifierErrorDomain] && [error code] == kPListErrorFailedAndErrorHasBeenReported;
1506}
1507
1508#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