Repository navigation
Expand file tree
/
Copy pathvalidate_response_templates.py
More file actions
271 lines (222 loc) Β· 9.8 KB
/
Copy pathvalidate_response_templates.py
File metadata and controls
271 lines (222 loc) Β· 9.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#!/usr/bin/env python3
"""
Response template validator for CI/CD pipelines.
This script validates response template JSON files against OpenAPI schemas defined in
mcopenapi_public.yml. It ensures template structure, field types, and required properties
match the API contract before deployment. Supports validation of individual templates,
merged template arrays, and manifest files. Exits with non-zero status on validation
failures to block CI builds with invalid templates.
Example usage:
# Validate individual template files (from repo root)
python .github/workflows/response_templates/validate_response_templates.py \
-d response_templates/ -s .github/workflows/response_templates/mcopenapi_public.yml
# Validate manifest and merged templates
python .github/workflows/response_templates/validate_response_templates.py \
-d response_templates/ -s .github/workflows/response_templates/mcopenapi_public.yml \
-m output/manifest.json --merged-dir output/
# Enable debug mode to dump resolved schemas
python .github/workflows/response_templates/validate_response_templates.py \
-d response_templates/ -s .github/workflows/response_templates/mcopenapi_public.yml --debug
"""
import argparse
import json
import sys
from pathlib import Path
from typing import Dict, Any, Tuple
import yaml
from jsonschema import Draft7Validator
def load_openapi_schema(yaml_path: Path, schema_name: str = 'ResponseTemplate', debug: bool = False) -> Dict[str, Any]:
"""Load the OpenAPI YAML file and extract the specified schema."""
with open(yaml_path, 'r') as f:
openapi_spec = yaml.safe_load(f)
# Extract the specified schema
target_schema = openapi_spec['components']['schemas'][schema_name]
# Resolve references to other schemas
schemas = openapi_spec['components']['schemas']
# We need to build a complete schema by resolving $ref
def resolve_refs(schema_obj: Any, schemas_dict: Dict) -> Any:
"""Recursively resolve $ref in schema objects."""
if isinstance(schema_obj, dict):
if '$ref' in schema_obj:
# Extract schema name from reference like "#/components/schemas/ResponseTemplatePhase"
ref_path = schema_obj['$ref'].split('/')[-1]
return resolve_refs(schemas_dict.get(ref_path, {}), schemas_dict)
else:
return {k: resolve_refs(v, schemas_dict) for k, v in schema_obj.items()}
elif isinstance(schema_obj, list):
return [resolve_refs(item, schemas_dict) for item in schema_obj]
else:
return schema_obj
resolved_schema = resolve_refs(target_schema, schemas)
# Add JSON Schema draft version
resolved_schema['$schema'] = 'http://json-schema.org/draft-07/schema#'
# Debug: dump resolved schema to file (only once per schema)
if debug:
debug_file = Path(f"debug_{schema_name}_schema.json")
if not debug_file.exists():
with open(debug_file, 'w') as f:
json.dump(resolved_schema, f, indent=2)
print(f"π Debug: Resolved schema dumped to {debug_file}")
else:
print(f"π Debug: Resolved schema already exists at {debug_file}")
return resolved_schema
def validate_json_file(json_path: Path, schema: Dict[str, Any]) -> Tuple[bool, str]:
"""
Validate a JSON file against the provided schema.
Returns (is_valid, error_message)
"""
try:
with open(json_path, 'r') as f:
json_data = json.load(f)
# Validate against schema
validator = Draft7Validator(schema)
errors = sorted(validator.iter_errors(json_data), key=lambda e: e.path)
if errors:
error_messages = []
for error in errors:
path = '.'.join(str(p) for p in error.path)
error_messages.append(f" - Path '{path}': {error.message}")
return False, '\n'.join(error_messages)
return True, "Valid"
except json.JSONDecodeError as e:
return False, f"JSON parsing error: {e}"
except Exception as e:
return False, f"Unexpected error: {e}"
def main():
parser = argparse.ArgumentParser(
description="Validate response_templates JSON files against the ResponseTemplate schema"
)
parser.add_argument(
'-d', '--directory',
type=str,
default='.',
help='Directory containing response template JSON files'
)
parser.add_argument(
'-s', '--schema',
type=str,
default='mcopenapi_public.yml',
help='Path to the OpenAPI YAML schema file'
)
parser.add_argument(
'-m', '--manifest',
type=str,
help='Path to manifest.json file to validate against ResponseTemplateManifest schema'
)
parser.add_argument(
'--merged-dir',
type=str,
help='Directory containing merged response template JSON files to validate against ResponseTemplateMerged schema'
)
parser.add_argument(
'--debug',
action='store_true',
help='Dump resolved schemas to JSON files for debugging'
)
args = parser.parse_args()
# Resolve paths
schema_path = Path(args.schema)
templates_dir = Path(args.directory)
if not schema_path.exists():
print(f"β Error: Schema file not found: {schema_path}")
sys.exit(1)
if not templates_dir.exists():
print(f"β Error: Templates directory not found: {templates_dir}")
sys.exit(1)
validation_results = []
# Validate manifest if provided
if args.manifest:
manifest_path = Path(args.manifest)
if not manifest_path.exists():
print(f"β Error: Manifest file not found: {manifest_path}")
sys.exit(1)
print(f"π Loading ResponseTemplateManifest schema from {schema_path}")
try:
manifest_schema = load_openapi_schema(schema_path, 'ResponseTemplateManifest', debug=args.debug)
print(f"β
Manifest schema loaded successfully")
except Exception as e:
print(f"β Error loading manifest schema: {e}")
sys.exit(1)
print(f"\nπ Validating manifest file: {manifest_path.name}\n")
print(f"Validating {manifest_path.name}...", end=" ")
is_valid, message = validate_json_file(manifest_path, manifest_schema)
validation_results.append((manifest_path.name, is_valid, message))
if is_valid:
print("β
")
else:
print("β")
print(message)
# Validate merged response templates if provided
if args.merged_dir:
merged_dir = Path(args.merged_dir)
if not merged_dir.exists():
print(f"β Error: Merged templates directory not found: {merged_dir}")
sys.exit(1)
print(f"\nπ Loading ResponseTemplateMerged schema from {schema_path}")
try:
merged_schema = load_openapi_schema(schema_path, 'ResponseTemplateMerged', debug=args.debug)
print(f"β
ResponseTemplateMerged schema loaded successfully")
except Exception as e:
print(f"β Error loading merged schema: {e}")
sys.exit(1)
# Find all JSON files in merged directory (excluding manifest.json)
merged_files = [f for f in merged_dir.glob('*.json')
if f.name != 'manifest.json']
if merged_files:
print(f"\nπ Found {len(merged_files)} merged response template file(s) to validate\n")
for json_file in sorted(merged_files):
print(f"Validating {json_file.name}...", end=" ")
is_valid, message = validate_json_file(json_file, merged_schema)
validation_results.append((json_file.name, is_valid, message))
if is_valid:
print("β
")
else:
print("β")
print(message)
else:
print(f"β οΈ No merged template JSON files found in {merged_dir}")
# Load ResponseTemplate schema
print(f"\nπ Loading ResponseTemplate schema from {schema_path}")
try:
schema = load_openapi_schema(schema_path, 'ResponseTemplate', debug=args.debug)
print(f"β
ResponseTemplate schema loaded successfully")
except Exception as e:
print(f"β Error loading schema: {e}")
sys.exit(1)
# Find all JSON files (excluding manifest if it's in the same directory)
json_files = [f for f in templates_dir.glob('*.json')
if not (args.manifest and f.name == Path(args.manifest).name)]
if not json_files:
if not args.manifest:
print(f"β οΈ No JSON files found in {templates_dir}")
sys.exit(0)
else:
print(f"\nπ Found {len(json_files)} response template file(s) to validate\n")
# Validate each file
for json_file in sorted(json_files):
print(f"Validating {json_file.name}...", end=" ")
is_valid, message = validate_json_file(json_file, schema)
validation_results.append((json_file.name, is_valid, message))
if is_valid:
print("β
")
else:
print("β")
print(message)
# Summary
print("\n" + "="*60)
print("VALIDATION SUMMARY")
print("="*60)
passed = sum(1 for _, is_valid, _ in validation_results if is_valid)
failed = len(validation_results) - passed
for filename, is_valid, message in validation_results:
status = "β
PASS" if is_valid else "β FAIL"
print(f"{status}: {filename}")
print(f"\nTotal: {len(validation_results)} | Passed: {passed} | Failed: {failed}")
if failed > 0:
print("\nβ Validation failed!")
sys.exit(1)
else:
print("\nβ
All files validated successfully!")
sys.exit(0)
if __name__ == "__main__":
main()