1#!/usr/bin/env python3
2
3import argparse
4import json
5import sys
6
7r"""
8Validates the PEL message registry JSON, which includes checking it against
9a JSON schema using the jsonschema module as well as doing some extra checks
10that can't be encoded in the schema.
11"""
12
13
14def 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
23    names = []
24    for entry in registry_json["PELs"]:
25        if entry["Name"] in names:
26            sys.exit("Found multiple uses of error {}".format(entry["Name"]))
27        else:
28            names.append(entry["Name"])
29
30
31def 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
39    reasonCodes = []
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            )
47        else:
48            reasonCodes.append(entry["SRC"]["ReasonCode"])
49
50
51def 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
60    for entry in registry_json["PELs"]:
61        # Don't check on "11" SRCs as those reason codes aren't supposed to
62        # match the component ID.
63        if entry["SRC"].get("Type", "") == "11":
64            continue
65
66        if "ComponentID" in entry:
67            id = int(entry["ComponentID"], 16)
68            reason_code = int(entry["SRC"]["ReasonCode"], 16)
69
70            if (id & 0xFF00) != (reason_code & 0xFF00):
71                sys.exit(
72                    "Found mismatching component ID {} vs reason "
73                    "code {} for error {}".format(
74                        entry["ComponentID"],
75                        entry["SRC"]["ReasonCode"],
76                        entry["Name"],
77                    )
78                )
79
80
81def check_message_args(registry_json):
82    r"""
83    Check that if the Message field uses the '%' style placeholders that there
84    are that many entries in the MessageArgSources field.  Also checks that
85    the MessageArgSources field is present but only if there are placeholders.
86
87    registry_json: The message registry JSON
88    """
89
90    for entry in registry_json["PELs"]:
91        num_placeholders = entry["Documentation"]["Message"].count("%")
92        if num_placeholders == 0:
93            continue
94
95        if "MessageArgSources" not in entry["Documentation"]:
96            sys.exit(
97                "Missing MessageArgSources property for error {}".format(
98                    entry["Name"]
99                )
100            )
101
102        if num_placeholders != len(
103            entry["Documentation"]["MessageArgSources"]
104        ):
105            sys.exit(
106                "Different number of placeholders found in "
107                "Message vs MessageArgSources for error {}".format(
108                    entry["Name"]
109                )
110            )
111
112
113def validate_schema(registry, schema):
114    r"""
115    Validates the passed in JSON against the passed in schema JSON
116
117    registry: Path of the file containing the registry JSON
118    schema:   Path of the file containing the schema JSON
119              Use None to skip the pure schema validation
120    """
121
122    with open(registry) as registry_handle:
123        registry_json = json.load(registry_handle)
124
125        if schema:
126            import jsonschema
127
128            with open(schema) as schema_handle:
129                schema_json = json.load(schema_handle)
130
131                try:
132                    jsonschema.validate(registry_json, schema_json)
133                except jsonschema.ValidationError as e:
134                    print(e)
135                    sys.exit("Schema validation failed")
136
137        check_duplicate_names(registry_json)
138
139        check_duplicate_reason_codes(registry_json)
140
141        check_component_id(registry_json)
142
143        check_message_args(registry_json)
144
145
146if __name__ == "__main__":
147    parser = argparse.ArgumentParser(
148        description="PEL message registry validator"
149    )
150
151    parser.add_argument(
152        "-s",
153        "--schema-file",
154        dest="schema_file",
155        help="The message registry JSON schema file",
156        required=True,
157    )
158
159    parser.add_argument(
160        "-r",
161        "--registry-file",
162        dest="registry_file",
163        help="The message registry JSON file",
164        required=True,
165    )
166
167    parser.add_argument(
168        "-k",
169        "--skip-schema-validation",
170        action="store_true",
171        dest="skip_schema",
172        help="Skip running schema validation. Only do the extra checks.",
173    )
174
175    args = parser.parse_args()
176
177    schema = args.schema_file
178    if args.skip_schema:
179        schema = None
180
181    validate_schema(args.registry_file, schema)
182