comparison contrib/tools/csv_to_fd @ 1487:5c2d061a8c8e

csv_to_fd: improve validation When an AVP Code or Name is duplicate, differentiate between a duplicate entry (AVPs are equivalent) versus an actual mismatch. Consistent AVP formatting in errors. Collect all errors and display at end of each file parse, instead of exiting on first error.
author Luke Mewburn <luke@mewburn.net>
date Fri, 27 Mar 2020 15:32:27 +1100
parents c0aa1e66c12e
children ae76ea63ee12
comparison
equal deleted inserted replaced
1486:eeb5706333c3 1487:5c2d061a8c8e
119 self.name, self.code)) 119 self.name, self.code))
120 if (self.datatype not in ( 120 if (self.datatype not in (
121 'OctetString', 'Integer32', 'Integer64', 'Unsigned32', 121 'OctetString', 'Integer32', 'Integer64', 'Unsigned32',
122 'Unsigned64', 'Float32', 'Float64', 'Grouped') 122 'Unsigned64', 'Float32', 'Float64', 'Grouped')
123 and self.datatype not in DERIVED_TO_BASE): 123 and self.datatype not in DERIVED_TO_BASE):
124 raise ValueError('AVP "{}" invalid data type "{}"'.format( 124 raise ValueError('{} invalid data type "{}"'.format(
125 self.name, self.datatype)) 125 self.description(), self.datatype))
126 # Validate flags 126 # Validate flags
127 flags = collections.Counter() 127 flags = collections.Counter()
128 for val, desc in [ 128 for val, desc in [
129 (self.must, 'Must'), 129 (self.must, 'Must'),
130 (self.may, 'May'), 130 (self.may, 'May'),
131 (self.shouldnot, 'Should Not'), 131 (self.shouldnot, 'Should Not'),
132 (self.mustnot, 'Must Not'), 132 (self.mustnot, 'Must Not'),
133 ]: 133 ]:
134 if not self._flags_re.match(val): 134 if not self._flags_re.match(val):
135 raise ValueError('AVP "{}" invalid {} Flags "{}"'.format( 135 raise ValueError('{} invalid {} Flags "{}"'.format(
136 self.name, desc, val)) 136 self.description(), desc, val))
137 flags.update(val) 137 flags.update(val)
138 # Check occurrence of M,V in Must,May,ShouldNot,MustNot 138 # Check occurrence of M,V in Must,May,ShouldNot,MustNot
139 for flag in 'MV': 139 for flag in 'MV':
140 # TODO: can AVP flags not appear at all? 140 # TODO: can AVP flags not appear at all?
141 # if flags[flag] == 0: 141 # if flags[flag] == 0:
142 # raise ValueError('AVP "{}" Flag "{}" not set'.format( 142 # raise ValueError('{} Flag "{}" not set'.format(
143 # self.name, flag)) 143 # self.description(), flag))
144 if flags[flag] > 1: 144 if flags[flag] > 1:
145 raise ValueError('AVP "{}" Flag "{}" set {} times'.format( 145 raise ValueError('{} Flag "{}" set {} times'.format(
146 self.name, flag, flags[flag])) 146 self.description(), flag, flags[flag]))
147 # Compare V presence against vendor 147 # Compare V presence against vendor
148 if 'V' in self.must: 148 if 'V' in self.must:
149 if self.vendor == 0: 149 if self.vendor == 0:
150 raise ValueError('AVP "{}" Flag "V" set for vendor 0'.format( 150 raise ValueError('{} Flag "V" set for vendor 0'.format(
151 self.name)) 151 self.description()))
152 else: 152 else:
153 if self.vendor != 0: 153 if self.vendor != 0:
154 raise ValueError( 154 raise ValueError('{} Flag "V" not set for vendor {}'.format(
155 'AVP "{}" Flag "V" not set for vendor {}'.format( 155 self.description(), self.vendor))
156 self.name, self.vendor))
157 156
158 @property 157 @property
159 def __dict__(self): 158 def __dict__(self):
160 return {s: getattr(self, s) for s in self.__slots__} 159 return {s: getattr(self, s) for s in self.__slots__}
160
161 def __eq__(self, other):
162 """Equality comparison of Avp instances.
163 Considered equal if name, vendor, code, datatype, and flags are equal.
164 """
165 if other is self:
166 return True
167 if type(other) is not type(self):
168 return NotImplemented
169 return (
170 other.name, other.vendor, other.code, other.datatype,
171 other.must, other.may, other.shouldnot, other.mustnot,
172 ) == (
173 self.name, self.vendor, self.code, self.datatype,
174 self.must, self.may, self.shouldnot, self.mustnot,
175 )
176
177 def __ne__(self, other):
178 return not self == other
179
180 def description(self):
181 return 'AVP "{}" ({})'.format(self.name, self.code)
161 182
162 183
163 class Processor(object): 184 class Processor(object):
164 """Interface for processor of Avp""" 185 """Interface for processor of Avp"""
165 186
390 if 'M' in flags: 411 if 'M' in flags:
391 result.append('M') 412 result.append('M')
392 return ''.join(result) 413 return ''.join(result)
393 414
394 415
416 def avp_conflict(description, avp, conflict):
417 """Raise error for duplicate or conflicting AVPs.
418 """
419 if avp == conflict:
420 raise ValueError(
421 '{} {} duplicated in'
422 ' file "{}" line {}'.format(
423 avp.description(), description,
424 conflict.filename, conflict.line_num))
425 else:
426 raise ValueError(
427 '{} {} conflicts with {}'
428 ' in file "{}" line {}'.format(
429 avp.description(), description,
430 conflict.description(),
431 conflict.filename, conflict.line_num))
432
433
395 def main(): 434 def main():
435 """Main application entry.
436 """
396 437
397 # Build dict of name: NameProcessor 438 # Build dict of name: NameProcessor
398 processors = { 439 processors = {
399 cls.cls_name(): cls 440 cls.cls_name(): cls
400 for cls in Processor.__subclasses__() 441 for cls in Processor.__subclasses__()
451 with open(filename, 'r') as csvfile: 492 with open(filename, 'r') as csvfile:
452 csvdata = csv.DictReader(csvfile, CSV_COLUMN_NAMES, 493 csvdata = csv.DictReader(csvfile, CSV_COLUMN_NAMES,
453 restkey='extra_cells') 494 restkey='extra_cells')
454 standard = '' 495 standard = ''
455 vendor = 0 496 vendor = 0
497 errors = []
456 for row in csvdata: 498 for row in csvdata:
457 try: 499 try:
458 if row['name'] in (None, '', 'Attribute Name'): 500 if row['name'] in (None, '', 'Attribute Name'):
459 continue 501 continue
460 elif row['name'].startswith('#'): 502 elif row['name'].startswith('#'):
477 standard=standard, vendor=vendor, 519 standard=standard, vendor=vendor,
478 **row) 520 **row)
479 # Ensure AVP vendor/code not already defined 521 # Ensure AVP vendor/code not already defined
480 if avp.code in avp_codes[avp.vendor]: 522 if avp.code in avp_codes[avp.vendor]:
481 conflict = avp_codes[avp.vendor][avp.code] 523 conflict = avp_codes[avp.vendor][avp.code]
482 raise ValueError( 524 avp_conflict('Code', avp, conflict)
483 'AVP vendor {} code {} already present'
484 ' in file "{}" line {}'.format(
485 avp.vendor, avp.code,
486 conflict.filename, conflict.line_num))
487 avp_codes[avp.vendor][avp.code] = avp 525 avp_codes[avp.vendor][avp.code] = avp
488 # Ensure AVP vendor/name not already defined 526 # Ensure AVP vendor/name not already defined
489 if avp.name in avp_names[avp.vendor]: 527 if avp.name in avp_names[avp.vendor]:
490 conflict = avp_names[avp.vendor][avp.name] 528 conflict = avp_names[avp.vendor][avp.name]
491 raise ValueError( 529 avp_conflict('Name', avp, conflict)
492 'AVP vendor {} name "{}" already present'
493 ' in file "{}" line {}'.format(
494 avp.vendor, avp.name,
495 conflict.filename, conflict.line_num))
496 avp_names[avp.vendor][avp.name] = avp 530 avp_names[avp.vendor][avp.name] = avp
497 # Process AVP 531 # Process AVP
498 avpproc.avp(avp) 532 avpproc.avp(avp)
499 except ValueError as e: 533 except ValueError as e:
500 sys.stderr.write('CSV file "{}" line {}: {}\n'.format( 534 errors.append('CSV file "{}" line {}: {}\n'.format(
501 filename, csvdata.line_num, e)) 535 filename, csvdata.line_num, e))
502 sys.exit(1) 536 if errors:
537 sys.stderr.write(''.join(errors))
538 sys.exit(1)
503 539
504 # Generate result 540 # Generate result
505 avpproc.generate() 541 avpproc.generate()
506 542
507 543
"Welcome to our mercurial repository"