NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

validator.py (19176B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\fsd\schemas\validator.py
      2 from __future__ import absolute_import
      3 import re
      4 import collections
      5 import os
      6 import yaml
      7 import decimal
      8 CLIENT = 'Client'
      9 SERVER = 'Server'
     10 EDITOR = 'Editor'
     11 ASCENDING = 'Ascending'
     12 DESCENDING = 'Descending'
     13 from . import persistence
     14 
     15 class SchemaMismatch(Exception):
     16 
     17     def __init__(self, path, message):
     18         self.filePath = None
     19         self.path = path
     20         self.message = message
     21 
     22     def SetFilePath(self, filePath):
     23         self.filePath = filePath
     24 
     25     def __str__(self):
     26         if self.filePath is None:
     27             return self.message
     28         else:
     29             return self.filePath + ':\n\t' + self.message
     30 
     31 
     32 class SchemaTypeError(SchemaMismatch):
     33     pass
     34 
     35 
     36 class SchemaComparisonError(SchemaMismatch):
     37     pass
     38 
     39 
     40 class SchemaObjectAttributeMissingError(SchemaMismatch):
     41     pass
     42 
     43 
     44 class SchemaObjectAttributeNotInSchemaError(SchemaMismatch):
     45     pass
     46 
     47 
     48 class ExternalReferenceError(SchemaMismatch):
     49     pass
     50 
     51 
     52 class ValidationException(SchemaMismatch):
     53 
     54     def __init__(self, path, errors):
     55         self.filePath = None
     56         self.path = path
     57         self.message = '\n\t'.join([ str(error) for error in errors ])
     58 
     59 
     60 class SchemaError(Exception):
     61 
     62     def __init__(self, path, message, node, data):
     63         self.path = path
     64         self.message = message
     65         self.node = node
     66         self.data = data
     67 
     68     def __str__(self):
     69         return self.message
     70 
     71 
     72 def ListContainsType(errorList, errorType):
     73     match = [ e for e in errorList if isinstance(e, errorType) ]
     74     return len(match) > 0
     75 
     76 
     77 def AllFloatValues(i):
     78     return all(map(lambda x: type(x) in (float,
     79      int,
     80      long,
     81      decimal.Decimal), i))
     82 
     83 
     84 def ValidateInt(schemaNode, o, path = 'root', state = {}):
     85     if type(o) not in (int, long):
     86         return [SchemaTypeError(path, '%s: Type Mismatch - should be an integer: %s' % (path, str(o)))]
     87     errors = []
     88     if 'min' in schemaNode:
     89         if o < schemaNode['min']:
     90             errors.append(SchemaComparisonError(path, '%s: Range mismatch - value %i is less than the minimum value of %i' % (path, o, schemaNode['min'])))
     91     elif 'exclusiveMin' in schemaNode:
     92         if o <= schemaNode['exclusiveMin']:
     93             errors.append(SchemaComparisonError(path, '%s: Range mismatch - value %i is less than or equal to the minimum value of %i' % (path, o, schemaNode['exclusiveMin'])))
     94     if 'max' in schemaNode:
     95         if o > schemaNode['max']:
     96             errors.append(SchemaComparisonError(path, '%s: Range mismatch - value %i is less than the maximum value of %i' % (path, o, schemaNode['max'])))
     97     elif 'exclusiveMax' in schemaNode:
     98         if o >= schemaNode['exclusiveMax']:
     99             errors.append(SchemaComparisonError(path, '%s: Range mismatch - value %i is less than or equal to the maximum value of %i' % (path, o, schemaNode['exclusiveMax'])))
    100     return errors
    101 
    102 
    103 def ValidateGenericId(schemaNode, o, path = 'root', state = {}):
    104     errors = ValidateInt(schemaNode, o, path, state)
    105     if not ListContainsType(errors, SchemaTypeError) and o < 0:
    106         errors.append(SchemaComparisonError(path, '%s: Range Mismatch - Integer Identifier should be > 0, this is %i' % (path, o)))
    107     return errors
    108 
    109 
    110 def ValidateTypeID(schemaNode, o, path = 'root', state = {}):
    111     errors = ValidateInt(schemaNode, o, path, state)
    112     if not ListContainsType(errors, SchemaTypeError) and o < 0:
    113         errors.append(SchemaComparisonError(path, '%s: Range Mismatch - typeIDs should be > 0, this is %i' % (path, o)))
    114     return errors
    115 
    116 
    117 def ValidateDogmaAttributeID(schemaNode, o, path = 'root', state = {}):
    118     errors = ValidateInt(schemaNode, o, path, state)
    119     if not ListContainsType(errors, SchemaTypeError) and o < 0:
    120         errors.append(SchemaComparisonError(path, '%s: Range Mismatch - dogmaAttributeIDs should be > 0, this is %i' % (path, o)))
    121     return errors
    122 
    123 
    124 def ValidateLocalizationID(schemaNode, o, path = 'root', state = {}):
    125     errors = ValidateInt(schemaNode, o, path, state)
    126     if not ListContainsType(errors, SchemaTypeError) and o < 0:
    127         errors.append(SchemaComparisonError(path, '%s: Range Mismatch - localizationIDs should be > 0, this is %i' % (path, o)))
    128     return errors
    129 
    130 
    131 def ValidateFloat(schemaNode, o, path = 'root', state = {}):
    132     errors = []
    133     if type(o) not in (float,
    134      int,
    135      long,
    136      decimal.Decimal):
    137         return [SchemaTypeError(path, '%s: Type Mismatch - should be a float' % path)]
    138     if 'min' in schemaNode:
    139         if o < schemaNode['min']:
    140             errors.append(SchemaComparisonError(path, '%s: Range mismatch - value %.1g is less than the minimum value of %.1g' % (path, o, schemaNode['min'])))
    141     elif 'exclusiveMin' in schemaNode:
    142         if o <= schemaNode['exclusiveMin']:
    143             errors.append(SchemaComparisonError(path, '%s: Range mismatch - value %.1g is less than or equal to the minimum value of %.1g' % (path, o, schemaNode['exclusiveMin'])))
    144     if 'max' in schemaNode:
    145         if o > schemaNode['max']:
    146             errors.append(SchemaComparisonError(path, '%s: Range mismatch - value %.1g is less than the maximum value of %.1g' % (path, o, schemaNode['max'])))
    147     elif 'exclusiveMax' in schemaNode:
    148         if o >= schemaNode['exclusiveMax']:
    149             errors.append(SchemaComparisonError(path, '%s: Range mismatch - value %.1g is less than or equal to the maximum value of %.1g' % (path, o, schemaNode['exclusiveMax'])))
    150     return errors
    151 
    152 
    153 def ValidateBool(schemaNode, o, path = 'root', state = {}):
    154     errors = []
    155     if type(o) is not bool:
    156         errors.append(SchemaTypeError(path, '%s: Type Mismatch - should be a bool' % path))
    157     return errors
    158 
    159 
    160 def ValidateCommonStringAttributes(schemaNode, o, path):
    161     typeName = 'string'
    162     if isinstance(o, unicode):
    163         typeName = 'unicode'
    164     errors = []
    165     if 'length' in schemaNode:
    166         if len(o) != schemaNode['length']:
    167             errors.append(SchemaComparisonError(path, "%s: Length mismatch - %s '%s' should be %i characters long" % (path,
    168              typeName,
    169              o.encode('utf-8'),
    170              schemaNode['length'])))
    171     if 'minLength' in schemaNode:
    172         if len(o) < schemaNode['minLength']:
    173             errors.append(SchemaComparisonError(path, "%s: Length mismatch - %s '%s' should be at least %i characters long" % (path,
    174              typeName,
    175              o.encode('utf-8'),
    176              schemaNode['minLength'])))
    177     if 'maxLength' in schemaNode:
    178         if len(o) > schemaNode['maxLength']:
    179             errors.append(SchemaComparisonError(path, "%s: Length mismatch - %s '%s' should be at most %i characters long" % (path,
    180              typeName,
    181              o.encode('utf-8'),
    182              schemaNode['maxLength'])))
    183     if 'regex' in schemaNode:
    184         if isinstance(o, unicode):
    185             regex = re.compile(schemaNode['regex'], re.UNICODE)
    186         else:
    187             regex = re.compile(schemaNode['regex'])
    188         if not regex.match(o):
    189             errors.append(SchemaComparisonError(path, "%s: Regex mismatch - %s '%s' does not match the regex '%s'" % (path,
    190              typeName,
    191              o.encode('utf-8'),
    192              schemaNode['regex'].encode('utf-8'))))
    193     return errors
    194 
    195 
    196 def ValidateString(schemaNode, o, path = 'root', state = {}):
    197     if type(o) is not str:
    198         return [SchemaTypeError(path, '%s: Type Mismatch - should be a string' % path)]
    199     return ValidateCommonStringAttributes(schemaNode, o, path)
    200 
    201 
    202 def ValidateUnicode(schemaNode, o, path = 'root', state = {}):
    203     if type(o) not in (unicode, str):
    204         return [SchemaTypeError(path, '%s: Type Mismatch - should be unicode' % path)]
    205     return ValidateCommonStringAttributes(schemaNode, o, path)
    206 
    207 
    208 def ValidateResPath(schemaNode, o, path = 'root', state = {}):
    209     errors = ValidateString(schemaNode, o, path, state)
    210     if not ListContainsType(errors, SchemaTypeError):
    211         if not o.startswith('res:/'):
    212             errors.append(SchemaComparisonError(path, "%s: Type Mismatch - resPaths should start with 'res:/' - %s" % (path, o)))
    213         if 'extensions' in schemaNode:
    214             fileName, fileExtension = os.path.splitext(o)
    215             fileExtension = fileExtension[1:]
    216             if fileExtension not in schemaNode['extensions']:
    217                 supportedExtension = ', '.join([ "'%s'" % extension for extension in schemaNode['extensions'] ])
    218                 errors.append(SchemaComparisonError(path, '%s: Type Mismatch - this resPath supports the following extensions: %s - %s' % (path, supportedExtension, o)))
    219         if ' ' in o:
    220             errors.append(SchemaComparisonError(path, '%s: Type Mismatch - resPaths should not contain spaces - %s' % (path, o)))
    221         if '\\' in o:
    222             errors.append(SchemaComparisonError(path, '%s: Type Mismatch - resPaths should not contain backslashes - %s' % (path, o)))
    223     return errors
    224 
    225 
    226 def ValidateVector2(schemaNode, o, path = 'root', state = {}):
    227     if type(o) not in (tuple, list):
    228         return [SchemaTypeError(path, '%s: Type Mismatch - should be a vector2' % path)]
    229     errors = []
    230     if len(o) != 2:
    231         errors.append(SchemaComparisonError(path, '%s: Length Mismatch - should be a vector2' % path))
    232     if not AllFloatValues(o):
    233         errors.append(SchemaComparisonError(path, '%s: Type Mismatch - should be a vector2 (contents are not all numeric!)' % path))
    234     return errors
    235 
    236 
    237 def ValidateVector3(schemaNode, o, path = 'root', state = {}):
    238     if type(o) not in (tuple, list):
    239         return [SchemaTypeError(path, '%(path)s: Type Mismatch - should be a vector3: (%(repr)s)' % {'path': path,
    240           'repr': repr(o)})]
    241     errors = []
    242     if len(o) != 3:
    243         errors.append(SchemaComparisonError(path, '%s: Length Mismatch - should be a vector3' % path))
    244     if not AllFloatValues(o):
    245         errors.append(SchemaComparisonError(path, '%s: Type Mismatch - should be a vector3 (contents are not all numeric!)' % path))
    246     return errors
    247 
    248 
    249 def ValidateVector4(schemaNode, o, path = 'root', state = {}):
    250     if type(o) not in (tuple, list):
    251         return [SchemaTypeError(path, '%s: Type Mismatch - should be a vector4' % path)]
    252     errors = []
    253     if len(o) != 4:
    254         errors.append(SchemaComparisonError(path, '%s: Length Mismatch - should be a vector4' % path))
    255     if not AllFloatValues(o):
    256         errors.append(SchemaComparisonError(path, '%s: Type Mismatch - should be a vector4 (contents are not all numeric!)' % path))
    257     return errors
    258 
    259 
    260 def ValidateObject(schemaNode, o, path = 'root', state = {}):
    261     errors = []
    262     if type(o) is not dict:
    263         return [SchemaTypeError(path, '%s: Type Mismatch - should be an object' % path)]
    264     for attrName, attrValue in schemaNode.get('attributes').iteritems():
    265         if _IsAttributeValueMissing(o, attrName, attrValue):
    266             errors.append(SchemaObjectAttributeMissingError(path, "%s: Attribute Mismatch - required attribute '%s' is missing!" % (path, attrName)))
    267         if attrName in o:
    268             errors.extend(Validate(attrValue, o[attrName], path + '.%s' % attrName, state))
    269 
    270     for attrName in o:
    271         if attrName not in schemaNode.get('attributes'):
    272             errors.append(SchemaObjectAttributeNotInSchemaError(path, "%s: Attribute Mismatch - attribute '%s' does not exist in the schema!" % (path, attrName)))
    273 
    274     return errors
    275 
    276 
    277 def _IsAttributeValueMissing(obj, attrName, attrValue):
    278     if attrName in obj:
    279         return False
    280     if attrValue.get('isOptional', False):
    281         return False
    282     if attrValue.get('generatedData', False):
    283         return False
    284     return True
    285 
    286 
    287 def ValidateList(schemaNode, o, path = 'root', state = {}):
    288     try:
    289         it = enumerate(o)
    290     except TypeError:
    291         return [SchemaTypeError(path, '%s: Type Mismatch - should be itterable' % path)]
    292 
    293     sortKey = schemaNode.get('sortKey', None)
    294     sortOrder = schemaNode.get('sortOrder', ASCENDING)
    295     originalLast = object()
    296     last = originalLast
    297     errors = []
    298     listOutOfOrder = False
    299     for index, i in it:
    300         if last is not originalLast:
    301             compare1 = last
    302             compare2 = i
    303             if sortKey is not None:
    304                 if sortKey not in last:
    305                     return [SchemaObjectAttributeMissingError(path + '[%i]' % (index - 1), "%s: Sort Attribute Missing - required attribute '%s' is missing!" % (path + '[%i]' % (index - 1), sortKey))]
    306                 if sortKey not in i:
    307                     return [SchemaObjectAttributeMissingError(path + '[%i]' % index, "%s: Sort Attribute Missing - required attribute '%s' is missing!" % (path + '[%i]' % index, sortKey))]
    308                 compare1 = last[sortKey]
    309                 compare2 = i[sortKey]
    310             if sortOrder not in (ASCENDING, DESCENDING):
    311                 errors.append(SchemaError(path, "sortOrder attribute in schema must be either '%s' or '%s', not %s" % (ASCENDING, DESCENDING, sortOrder), schemaNode, o))
    312             if sortOrder == ASCENDING and not listOutOfOrder:
    313                 if compare1 >= compare2:
    314                     errors.append(SchemaComparisonError(path + '[%i]' % index, '%s: Order Error! Sort key: %s. List should be sorted ASCENDING. %s >= %s!' % (path + '[%i]' % index,
    315                      sortKey,
    316                      str(compare1),
    317                      str(compare2))))
    318                     listOutOfOrder = True
    319             elif sortOrder == DESCENDING and not listOutOfOrder:
    320                 if compare1 <= compare2:
    321                     errors.append(SchemaComparisonError(path + '[%i]' % index, '%s: Order Error! Sort key: %s. List should be sorted DESCENDING. %s <= %s!' % (path + '[%i]' % index,
    322                      sortKey,
    323                      str(compare1),
    324                      str(compare2))))
    325                     listOutOfOrder = True
    326         errors.extend(Validate(schemaNode.get('itemTypes'), i, path + '[%i]' % index, state))
    327         last = i
    328 
    329     return errors
    330 
    331 
    332 def ValidateDict(schemaNode, o, path = 'root', state = {}):
    333     if type(o) not in (dict, collections.OrderedDict, collections.defaultdict):
    334         return [SchemaTypeError(path, '%s: Type Mismatch - should be itterable' % path)]
    335     errors = []
    336     for dictKey, dictValue in o.iteritems():
    337         errors.extend(Validate(schemaNode['keyTypes'], dictKey, path + '<%s>' % str(dictKey), state))
    338         errors.extend(Validate(schemaNode['valueTypes'], dictValue, path + '[%s]' % str(dictKey), state))
    339 
    340     return errors
    341 
    342 
    343 def ValidateEnum(schemaNode, o, path = 'root', state = {}):
    344     if o not in schemaNode.get('values', {}):
    345         return [SchemaTypeError(path, '%s: Enum value not found in schema: %s' % (path, repr(o)))]
    346     return []
    347 
    348 
    349 def ValidateUnion(schemaNode, o, path = 'root', state = {}):
    350     for s in schemaNode['optionTypes']:
    351         validationErrors = Validate(s, o, path, state)
    352         if len(validationErrors) == 0:
    353             break
    354     else:
    355         return [SchemaTypeError(path, '%s: Did not match any of the possible schema types: %s' % (path, repr(o)))]
    356 
    357     return []
    358 
    359 
    360 builtInValdationFunctions = {'int': ValidateInt,
    361  'typeID': ValidateTypeID,
    362  'groupID': ValidateInt,
    363  'categoryID': ValidateInt,
    364  'localizationID': ValidateLocalizationID,
    365  'float': ValidateFloat,
    366  'vector2': ValidateVector2,
    367  'vector3': ValidateVector3,
    368  'vector4': ValidateVector4,
    369  'dict': ValidateDict,
    370  'list': ValidateList,
    371  'object': ValidateObject,
    372  'enum': ValidateEnum,
    373  'string': ValidateString,
    374  'unicode': ValidateUnicode,
    375  'resPath': ValidateResPath,
    376  'bool': ValidateBool,
    377  'union': ValidateUnion,
    378  'color': ValidateVector4,
    379  'dogmaAttributeID': ValidateDogmaAttributeID,
    380  'npcTag': ValidateGenericId,
    381  'deploymentType': ValidateGenericId,
    382  'npcEnemyFleetTypeID': ValidateGenericId,
    383  'groupBehaviorTreeID': ValidateGenericId,
    384  'npcCorporationID': ValidateGenericId,
    385  'spawnTableID': ValidateGenericId,
    386  'npcFleetCounterTableID': ValidateGenericId,
    387  'dungeonID': ValidateGenericId,
    388  'typeListID': ValidateGenericId,
    389  'npcFleetTypeID': ValidateGenericId,
    390  'metaGroupID': ValidateGenericId,
    391  'fsdReference': ValidateGenericId,
    392  'raceID': ValidateGenericId,
    393  'marketGroupID': ValidateGenericId,
    394  'ShipGroupID': ValidateGenericId,
    395  'certificateTemplateID': ValidateGenericId,
    396  'factionID': ValidateGenericId}
    397 
    398 def ValidateReference(schemaNode, o, path, state):
    399     referenceInfo = schemaNode.get('reference')
    400     if not state.get('validateReferences', False):
    401         return []
    402     if 'schema' in referenceInfo and 'branchRoot' in state:
    403         if 'openSchemas' not in state:
    404             state['openSchemas'] = {}
    405             state['openData'] = {}
    406         otherSchemaPath = os.path.abspath(os.path.join(state['branchRoot'], referenceInfo['schema']))
    407         if 'schemaCache' in state and otherSchemaPath in state['schemaCache']:
    408             otherSchema = state['schemaCache'].Get(otherSchemaPath)
    409         elif otherSchemaPath not in state['openSchemas']:
    410             with open(otherSchemaPath, 'r') as otherSchemaFile:
    411                 otherSchema = state['openSchemas'][otherSchemaPath] = persistence.LoadSchema(otherSchemaFile)
    412         else:
    413             otherSchema = state['openSchemas'][otherSchemaPath]
    414         editorFile = otherSchema['editorFile'] % {'key': o}
    415         fullEditorFilePath = os.path.abspath(os.path.join(state['branchRoot'], editorFile))
    416         if 'dataCache' in state and fullEditorFilePath in state['dataCache']:
    417             otherData = state['dataCache'].Get(fullEditorFilePath)
    418         elif fullEditorFilePath not in state['openData']:
    419             with open(fullEditorFilePath, 'r') as otherDataFile:
    420                 otherData = state['openData'][fullEditorFilePath] = yaml.load(otherDataFile)
    421         else:
    422             otherData = state['openData'][fullEditorFilePath]
    423         if o not in otherData:
    424             errorString = '%(path)s: ' + referenceInfo['errorMessage']
    425             return [ExternalReferenceError(path, errorString % {'path': path,
    426               'key': str(o),
    427               'dataFile': fullEditorFilePath})]
    428     return []
    429 
    430 
    431 def Validate(schemaNode, o, path = 'root', state = {}):
    432     nodeType = schemaNode.get('type', None)
    433     if nodeType is None:
    434         return [SchemaError(path, "%s: Could not find a 'type' for the schema node" % path, schemaNode, o)]
    435     errors = []
    436     if schemaNode.get('generatedData', False):
    437         return errors
    438     if 'reference' in schemaNode:
    439         errors.extend(ValidateReference(schemaNode, o, path, state))
    440     if nodeType in state.get('overrides', {}):
    441         errors.extend(state.get('overrides', {})[nodeType](schemaNode, o, path, state))
    442     elif nodeType in builtInValdationFunctions:
    443         errors.extend(builtInValdationFunctions[nodeType](schemaNode, o, path, state))
    444     else:
    445         return [SchemaError(path, "%s: Could not find a known 'type' for the schema node: %s" % (path, nodeType), schemaNode, o)]
    446     return errors
    447 
    448 
    449 def ValidateWithExceptions(schemaNode, o, path = 'root', state = {}, showOnlyFirstError = True):
    450     errors = Validate(schemaNode, o, path, state)
    451     if len(errors):
    452         if showOnlyFirstError:
    453             raise ValidationException(path, [errors[0]])
    454         else:
    455             raise ValidationException(path, errors)