| Matt Spinler | 446d2b9 | 2020-03-05 16:49:14 -0600 | [diff] [blame] | 1 | #!/usr/bin/env python3 | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 2 |  | 
|  | 3 | import argparse | 
|  | 4 | import json | 
|  | 5 | import sys | 
|  | 6 |  | 
|  | 7 | r""" | 
|  | 8 | Validates the PEL message registry JSON, which includes checking it against | 
|  | 9 | a JSON schema using the jsonschema module as well as doing some extra checks | 
|  | 10 | that can't be encoded in the schema. | 
|  | 11 | """ | 
|  | 12 |  | 
|  | 13 |  | 
|  | 14 | def check_duplicate_names(registry_json): | 
|  | 15 | r""" | 
|  | 16 | Check that there aren't any message registry entries with the same | 
|  | 17 | 'Name' field.  There may be a use case for this in the future, but there | 
|  | 18 | isn't right now. | 
|  | 19 |  | 
|  | 20 | registry_json: The message registry JSON | 
|  | 21 | """ | 
|  | 22 |  | 
| Matt Spinler | 446d2b9 | 2020-03-05 16:49:14 -0600 | [diff] [blame] | 23 | names = [] | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 24 | for entry in registry_json["PELs"]: | 
|  | 25 | if entry["Name"] in names: | 
|  | 26 | sys.exit("Found multiple uses of error {}".format(entry["Name"])) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 27 | else: | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 28 | names.append(entry["Name"]) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 29 |  | 
|  | 30 |  | 
| Matt Spinler | 55a6978 | 2019-10-25 13:49:30 -0500 | [diff] [blame] | 31 | def check_duplicate_reason_codes(registry_json): | 
|  | 32 | r""" | 
|  | 33 | Check that there aren't any message registry entries with the same | 
|  | 34 | 'ReasonCode' field. | 
|  | 35 |  | 
|  | 36 | registry_json: The message registry JSON | 
|  | 37 | """ | 
|  | 38 |  | 
| Matt Spinler | 446d2b9 | 2020-03-05 16:49:14 -0600 | [diff] [blame] | 39 | reasonCodes = [] | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 40 | for entry in registry_json["PELs"]: | 
|  | 41 | if entry["SRC"]["ReasonCode"] in reasonCodes: | 
|  | 42 | sys.exit( | 
|  | 43 | "Found duplicate SRC reason code {}".format( | 
|  | 44 | entry["SRC"]["ReasonCode"] | 
|  | 45 | ) | 
|  | 46 | ) | 
| Matt Spinler | 55a6978 | 2019-10-25 13:49:30 -0500 | [diff] [blame] | 47 | else: | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 48 | reasonCodes.append(entry["SRC"]["ReasonCode"]) | 
| Matt Spinler | 55a6978 | 2019-10-25 13:49:30 -0500 | [diff] [blame] | 49 |  | 
|  | 50 |  | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 51 | def check_component_id(registry_json): | 
|  | 52 | r""" | 
|  | 53 | Check that the upper byte of the ComponentID field matches the upper byte | 
|  | 54 | of the ReasonCode field, but not on "11" type SRCs where they aren't | 
|  | 55 | supposed to match. | 
|  | 56 |  | 
|  | 57 | registry_json: The message registry JSON | 
|  | 58 | """ | 
|  | 59 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 60 | for entry in registry_json["PELs"]: | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 61 |  | 
|  | 62 | # Don't check on "11" SRCs as those reason codes aren't supposed to | 
|  | 63 | # match the component ID. | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 64 | if entry["SRC"].get("Type", "") == "11": | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 65 | continue | 
|  | 66 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 67 | if "ComponentID" in entry: | 
|  | 68 | id = int(entry["ComponentID"], 16) | 
|  | 69 | reason_code = int(entry["SRC"]["ReasonCode"], 16) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 70 |  | 
|  | 71 | if (id & 0xFF00) != (reason_code & 0xFF00): | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 72 | sys.exit( | 
|  | 73 | "Found mismatching component ID {} vs reason " | 
|  | 74 | "code {} for error {}".format( | 
|  | 75 | entry["ComponentID"], | 
|  | 76 | entry["SRC"]["ReasonCode"], | 
|  | 77 | entry["Name"], | 
|  | 78 | ) | 
|  | 79 | ) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 80 |  | 
|  | 81 |  | 
|  | 82 | def check_message_args(registry_json): | 
|  | 83 | r""" | 
|  | 84 | Check that if the Message field uses the '%' style placeholders that there | 
|  | 85 | are that many entries in the MessageArgSources field.  Also checks that | 
|  | 86 | the MessageArgSources field is present but only if there are placeholders. | 
|  | 87 |  | 
|  | 88 | registry_json: The message registry JSON | 
|  | 89 | """ | 
|  | 90 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 91 | for entry in registry_json["PELs"]: | 
|  | 92 | num_placeholders = entry["Documentation"]["Message"].count("%") | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 93 | if num_placeholders == 0: | 
|  | 94 | continue | 
|  | 95 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 96 | if "MessageArgSources" not in entry["Documentation"]: | 
|  | 97 | sys.exit( | 
|  | 98 | "Missing MessageArgSources property for error {}".format( | 
|  | 99 | entry["Name"] | 
|  | 100 | ) | 
|  | 101 | ) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 102 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 103 | if num_placeholders != len( | 
|  | 104 | entry["Documentation"]["MessageArgSources"] | 
|  | 105 | ): | 
|  | 106 | sys.exit( | 
|  | 107 | "Different number of placeholders found in " | 
|  | 108 | "Message vs MessageArgSources for error {}".format( | 
|  | 109 | entry["Name"] | 
|  | 110 | ) | 
|  | 111 | ) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 112 |  | 
|  | 113 |  | 
|  | 114 | def validate_schema(registry, schema): | 
|  | 115 | r""" | 
|  | 116 | Validates the passed in JSON against the passed in schema JSON | 
|  | 117 |  | 
|  | 118 | registry: Path of the file containing the registry JSON | 
|  | 119 | schema:   Path of the file containing the schema JSON | 
|  | 120 | Use None to skip the pure schema validation | 
|  | 121 | """ | 
|  | 122 |  | 
|  | 123 | with open(registry) as registry_handle: | 
|  | 124 | registry_json = json.load(registry_handle) | 
|  | 125 |  | 
|  | 126 | if schema: | 
|  | 127 |  | 
|  | 128 | import jsonschema | 
|  | 129 |  | 
|  | 130 | with open(schema) as schema_handle: | 
|  | 131 | schema_json = json.load(schema_handle) | 
|  | 132 |  | 
|  | 133 | try: | 
|  | 134 | jsonschema.validate(registry_json, schema_json) | 
|  | 135 | except jsonschema.ValidationError as e: | 
|  | 136 | print(e) | 
|  | 137 | sys.exit("Schema validation failed") | 
|  | 138 |  | 
|  | 139 | check_duplicate_names(registry_json) | 
|  | 140 |  | 
| Matt Spinler | 55a6978 | 2019-10-25 13:49:30 -0500 | [diff] [blame] | 141 | check_duplicate_reason_codes(registry_json) | 
|  | 142 |  | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 143 | check_component_id(registry_json) | 
|  | 144 |  | 
|  | 145 | check_message_args(registry_json) | 
|  | 146 |  | 
|  | 147 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 148 | if __name__ == "__main__": | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 149 |  | 
|  | 150 | parser = argparse.ArgumentParser( | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 151 | description="PEL message registry processor" | 
|  | 152 | ) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 153 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 154 | parser.add_argument( | 
|  | 155 | "-v", | 
|  | 156 | "--validate", | 
|  | 157 | action="store_true", | 
|  | 158 | dest="validate", | 
|  | 159 | help="Validate the JSON using the schema", | 
|  | 160 | ) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 161 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 162 | parser.add_argument( | 
|  | 163 | "-s", | 
|  | 164 | "--schema-file", | 
|  | 165 | dest="schema_file", | 
|  | 166 | help="The message registry JSON schema file", | 
|  | 167 | ) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 168 |  | 
| Patrick Williams | e6555f5 | 2022-08-04 13:56:17 -0500 | [diff] [blame] | 169 | parser.add_argument( | 
|  | 170 | "-r", | 
|  | 171 | "--registry-file", | 
|  | 172 | dest="registry_file", | 
|  | 173 | help="The message registry JSON file", | 
|  | 174 | ) | 
|  | 175 | parser.add_argument( | 
|  | 176 | "-k", | 
|  | 177 | "--skip-schema-validation", | 
|  | 178 | action="store_true", | 
|  | 179 | dest="skip_schema", | 
|  | 180 | help="Skip running schema validation. " "Only do the extra checks.", | 
|  | 181 | ) | 
| Matt Spinler | 5cb5deb | 2019-09-27 13:51:36 -0500 | [diff] [blame] | 182 |  | 
|  | 183 | args = parser.parse_args() | 
|  | 184 |  | 
|  | 185 | if args.validate: | 
|  | 186 | if not args.schema_file: | 
|  | 187 | sys.exit("Schema file required") | 
|  | 188 |  | 
|  | 189 | if not args.registry_file: | 
|  | 190 | sys.exit("Registry file required") | 
|  | 191 |  | 
|  | 192 | schema = args.schema_file | 
|  | 193 | if args.skip_schema: | 
|  | 194 | schema = None | 
|  | 195 |  | 
|  | 196 | validate_schema(args.registry_file, schema) |