|
| 1 | +#!/usr/bin/env ruby |
| 2 | +require 'yaml' |
| 3 | +require 'json' |
| 4 | + |
| 5 | +puts "=" * 80 |
| 6 | +puts "PHASE 9: FINAL VALIDATION" |
| 7 | +puts "=" * 80 |
| 8 | +puts "" |
| 9 | + |
| 10 | +# Load files |
| 11 | +puts "Loading files..." |
| 12 | +source = YAML.unsafe_load_file('openapi/v20111101.yaml') |
| 13 | +target = YAML.unsafe_load_file('openapi/mx_platform_api.yml') |
| 14 | +models = YAML.unsafe_load_file('openapi/models.yaml') |
| 15 | +parameters = YAML.unsafe_load_file('openapi/parameters.yaml') |
| 16 | + |
| 17 | +puts "✓ All files loaded successfully" |
| 18 | +puts "" |
| 19 | + |
| 20 | +# Track validation results |
| 21 | +issues = [] |
| 22 | +warnings = [] |
| 23 | + |
| 24 | +# 1. SCHEMA VALIDATION |
| 25 | +puts "1. VALIDATING SCHEMAS" |
| 26 | +puts "-" * 40 |
| 27 | + |
| 28 | +source_schemas = source.dig('components', 'schemas') || {} |
| 29 | +target_schemas = target.dig('components', 'schemas') || {} |
| 30 | + |
| 31 | +# Check schema count |
| 32 | +puts "Schema counts:" |
| 33 | +puts " v20111101.yaml (models.yaml): #{models.keys.count}" |
| 34 | +puts " mx_platform_api.yml: #{target_schemas.keys.count}" |
| 35 | + |
| 36 | +missing_schemas = models.keys - target_schemas.keys |
| 37 | +extra_schemas = target_schemas.keys - models.keys |
| 38 | + |
| 39 | +if missing_schemas.any? |
| 40 | + issues << "Missing #{missing_schemas.count} schemas: #{missing_schemas.join(', ')}" |
| 41 | + puts " ❌ Missing schemas: #{missing_schemas.join(', ')}" |
| 42 | +else |
| 43 | + puts " ✓ No missing schemas" |
| 44 | +end |
| 45 | + |
| 46 | +if extra_schemas.any? |
| 47 | + issues << "Extra #{extra_schemas.count} schemas: #{extra_schemas.join(', ')}" |
| 48 | + puts " ❌ Extra schemas: #{extra_schemas.join(', ')}" |
| 49 | +else |
| 50 | + puts " ✓ No extra schemas" |
| 51 | +end |
| 52 | + |
| 53 | +# Validate schema structures |
| 54 | +puts "\nValidating schema structures..." |
| 55 | +schema_field_mismatches = 0 |
| 56 | + |
| 57 | +models.each do |schema_name, schema_def| |
| 58 | + next unless target_schemas[schema_name] |
| 59 | + next unless schema_def['properties'] |
| 60 | + |
| 61 | + source_props = schema_def['properties'].keys.sort |
| 62 | + target_props = (target_schemas[schema_name]['properties'] || {}).keys.sort |
| 63 | + |
| 64 | + if source_props != target_props |
| 65 | + schema_field_mismatches += 1 |
| 66 | + missing = source_props - target_props |
| 67 | + extra = target_props - source_props |
| 68 | + |
| 69 | + if missing.any? |
| 70 | + issues << "#{schema_name}: missing fields #{missing.join(', ')}" |
| 71 | + end |
| 72 | + if extra.any? |
| 73 | + issues << "#{schema_name}: extra fields #{extra.join(', ')}" |
| 74 | + end |
| 75 | + end |
| 76 | +end |
| 77 | + |
| 78 | +if schema_field_mismatches > 0 |
| 79 | + puts " ❌ #{schema_field_mismatches} schemas have field mismatches" |
| 80 | +else |
| 81 | + puts " ✓ All schema structures match" |
| 82 | +end |
| 83 | + |
| 84 | +puts "" |
| 85 | + |
| 86 | +# 2. PARAMETER VALIDATION |
| 87 | +puts "2. VALIDATING PARAMETERS" |
| 88 | +puts "-" * 40 |
| 89 | + |
| 90 | +target_params = target.dig('components', 'parameters') || {} |
| 91 | + |
| 92 | +puts "Parameter counts:" |
| 93 | +puts " v20111101.yaml (parameters.yaml): #{parameters.keys.count}" |
| 94 | +puts " mx_platform_api.yml: #{target_params.keys.count}" |
| 95 | + |
| 96 | +missing_params = parameters.keys - target_params.keys |
| 97 | +extra_params = target_params.keys - parameters.keys |
| 98 | + |
| 99 | +if missing_params.any? |
| 100 | + issues << "Missing #{missing_params.count} parameters: #{missing_params.join(', ')}" |
| 101 | + puts " ❌ Missing parameters: #{missing_params.join(', ')}" |
| 102 | +else |
| 103 | + puts " ✓ No missing parameters" |
| 104 | +end |
| 105 | + |
| 106 | +if extra_params.any? |
| 107 | + issues << "Extra #{extra_params.count} parameters: #{extra_params.join(', ')}" |
| 108 | + puts " ❌ Extra parameters: #{extra_params.join(', ')}" |
| 109 | +else |
| 110 | + puts " ✓ No extra parameters" |
| 111 | +end |
| 112 | + |
| 113 | +puts "" |
| 114 | + |
| 115 | +# 3. PATH VALIDATION |
| 116 | +puts "3. VALIDATING PATHS" |
| 117 | +puts "-" * 40 |
| 118 | + |
| 119 | +source_paths = (source['paths'] || {}).keys.sort |
| 120 | +target_paths = (target['paths'] || {}).keys.sort |
| 121 | + |
| 122 | +puts "Path counts:" |
| 123 | +puts " v20111101.yaml: #{source_paths.count}" |
| 124 | +puts " mx_platform_api.yml: #{target_paths.count}" |
| 125 | + |
| 126 | +missing_paths = source_paths - target_paths |
| 127 | +extra_paths = target_paths - source_paths |
| 128 | + |
| 129 | +if missing_paths.any? |
| 130 | + issues << "Missing #{missing_paths.count} paths" |
| 131 | + puts " ❌ Missing paths:" |
| 132 | + missing_paths.each { |path| puts " - #{path}" } |
| 133 | +else |
| 134 | + puts " ✓ No missing paths" |
| 135 | +end |
| 136 | + |
| 137 | +if extra_paths.any? |
| 138 | + issues << "Extra #{extra_paths.count} paths" |
| 139 | + puts " ❌ Extra paths:" |
| 140 | + extra_paths.each { |path| puts " - #{path}" } |
| 141 | +else |
| 142 | + puts " ✓ No extra paths" |
| 143 | +end |
| 144 | + |
| 145 | +# Validate operations per path |
| 146 | +puts "\nValidating operations per path..." |
| 147 | +operation_mismatches = 0 |
| 148 | + |
| 149 | +source_paths.each do |path| |
| 150 | + next unless target['paths'][path] |
| 151 | + |
| 152 | + source_ops = (source['paths'][path] || {}).keys.reject { |k| k.start_with?('$') || k == 'parameters' }.sort |
| 153 | + target_ops = (target['paths'][path] || {}).keys.reject { |k| k.start_with?('$') || k == 'parameters' }.sort |
| 154 | + |
| 155 | + if source_ops != target_ops |
| 156 | + operation_mismatches += 1 |
| 157 | + missing = source_ops - target_ops |
| 158 | + extra = target_ops - source_ops |
| 159 | + |
| 160 | + if missing.any? |
| 161 | + issues << "#{path}: missing operations #{missing.join(', ')}" |
| 162 | + end |
| 163 | + if extra.any? |
| 164 | + issues << "#{path}: extra operations #{extra.join(', ')}" |
| 165 | + end |
| 166 | + end |
| 167 | +end |
| 168 | + |
| 169 | +if operation_mismatches > 0 |
| 170 | + puts " ❌ #{operation_mismatches} paths have operation mismatches" |
| 171 | +else |
| 172 | + puts " ✓ All path operations match" |
| 173 | +end |
| 174 | + |
| 175 | +puts "" |
| 176 | + |
| 177 | +# 4. EXTERNAL REFERENCE CHECK |
| 178 | +puts "4. CHECKING FOR EXTERNAL REFERENCES" |
| 179 | +puts "-" * 40 |
| 180 | + |
| 181 | +yaml_content = File.read('openapi/mx_platform_api.yml') |
| 182 | +external_refs = yaml_content.scan(/\$ref:\s*['"]\.\/schemas\//) |
| 183 | + |
| 184 | +if external_refs.any? |
| 185 | + issues << "Found #{external_refs.count} external references" |
| 186 | + puts " ❌ External references found: #{external_refs.count}" |
| 187 | +else |
| 188 | + puts " ✓ No external references (file is self-contained)" |
| 189 | +end |
| 190 | + |
| 191 | +puts "" |
| 192 | + |
| 193 | +# 5. TAG VALIDATION |
| 194 | +puts "5. VALIDATING TAGS" |
| 195 | +puts "-" * 40 |
| 196 | + |
| 197 | +# Collect all tags used in paths |
| 198 | +source_tags = [] |
| 199 | +target_tags = [] |
| 200 | + |
| 201 | +source['paths']&.each do |path, path_def| |
| 202 | + path_def.each do |method, op_def| |
| 203 | + next if method.start_with?('$') || method == 'parameters' |
| 204 | + next unless op_def.is_a?(Hash) |
| 205 | + source_tags.concat(op_def['tags'] || []) |
| 206 | + end |
| 207 | +end |
| 208 | + |
| 209 | +target['paths']&.each do |path, path_def| |
| 210 | + path_def.each do |method, op_def| |
| 211 | + next if method.start_with?('$') || method == 'parameters' |
| 212 | + next unless op_def.is_a?(Hash) |
| 213 | + target_tags.concat(op_def['tags'] || []) |
| 214 | + end |
| 215 | +end |
| 216 | + |
| 217 | +source_tags = source_tags.uniq.sort |
| 218 | +target_tags = target_tags.uniq.sort |
| 219 | + |
| 220 | +puts "Tag counts:" |
| 221 | +puts " v20111101.yaml: #{source_tags.count} unique tags" |
| 222 | +puts " mx_platform_api.yml: #{target_tags.count} unique tags" |
| 223 | + |
| 224 | +# Check for generic tags |
| 225 | +generic_tags = target_tags.select { |tag| tag == 'mx_platform' || tag.downcase.include?('platform') } |
| 226 | + |
| 227 | +if generic_tags.any? |
| 228 | + warnings << "Found #{generic_tags.count} generic tags: #{generic_tags.join(', ')}" |
| 229 | + puts " ⚠️ Generic tags found: #{generic_tags.join(', ')}" |
| 230 | +else |
| 231 | + puts " ✓ No generic tags" |
| 232 | +end |
| 233 | + |
| 234 | +missing_tags = source_tags - target_tags |
| 235 | +extra_tags = target_tags - source_tags |
| 236 | + |
| 237 | +if missing_tags.any? |
| 238 | + warnings << "Missing #{missing_tags.count} tags: #{missing_tags.join(', ')}" |
| 239 | + puts " ⚠️ Missing tags: #{missing_tags.join(', ')}" |
| 240 | +end |
| 241 | + |
| 242 | +if extra_tags.any? |
| 243 | + warnings << "Extra #{extra_tags.count} tags: #{extra_tags.join(', ')}" |
| 244 | + puts " ⚠️ Extra tags: #{extra_tags.join(', ')}" |
| 245 | +end |
| 246 | + |
| 247 | +puts "" |
| 248 | + |
| 249 | +# 6. TYPE CONSISTENCY CHECK |
| 250 | +puts "6. VALIDATING TYPE CONSISTENCY" |
| 251 | +puts "-" * 40 |
| 252 | + |
| 253 | +type_mismatches = 0 |
| 254 | + |
| 255 | +models.each do |schema_name, schema_def| |
| 256 | + next unless target_schemas[schema_name] |
| 257 | + next unless schema_def['properties'] |
| 258 | + |
| 259 | + schema_def['properties'].each do |field_name, field_def| |
| 260 | + target_field = target_schemas[schema_name].dig('properties', field_name) |
| 261 | + next unless target_field |
| 262 | + |
| 263 | + source_type = field_def['type'] |
| 264 | + target_type = target_field['type'] |
| 265 | + |
| 266 | + if source_type && target_type && source_type != target_type |
| 267 | + type_mismatches += 1 |
| 268 | + issues << "Type mismatch: #{schema_name}.#{field_name} (source: #{source_type}, target: #{target_type})" |
| 269 | + end |
| 270 | + end |
| 271 | +end |
| 272 | + |
| 273 | +if type_mismatches > 0 |
| 274 | + puts " ❌ Found #{type_mismatches} type mismatches" |
| 275 | +else |
| 276 | + puts " ✓ All types match" |
| 277 | +end |
| 278 | + |
| 279 | +puts "" |
| 280 | + |
| 281 | +# 7. FILE STRUCTURE VALIDATION |
| 282 | +puts "7. VALIDATING FILE STRUCTURE" |
| 283 | +puts "-" * 40 |
| 284 | + |
| 285 | +required_sections = ['openapi', 'info', 'servers', 'paths', 'components'] |
| 286 | +missing_sections = required_sections.select { |section| !target[section] } |
| 287 | + |
| 288 | +if missing_sections.any? |
| 289 | + issues << "Missing required sections: #{missing_sections.join(', ')}" |
| 290 | + puts " ❌ Missing sections: #{missing_sections.join(', ')}" |
| 291 | +else |
| 292 | + puts " ✓ All required sections present" |
| 293 | +end |
| 294 | + |
| 295 | +# Check components subsections |
| 296 | +required_components = ['schemas', 'parameters', 'securitySchemes'] |
| 297 | +missing_components = required_components.select { |comp| !target.dig('components', comp) } |
| 298 | + |
| 299 | +if missing_components.any? |
| 300 | + issues << "Missing component sections: #{missing_components.join(', ')}" |
| 301 | + puts " ❌ Missing component sections: #{missing_components.join(', ')}" |
| 302 | +else |
| 303 | + puts " ✓ All component sections present" |
| 304 | +end |
| 305 | + |
| 306 | +puts "" |
| 307 | + |
| 308 | +# FINAL SUMMARY |
| 309 | +puts "=" * 80 |
| 310 | +puts "VALIDATION SUMMARY" |
| 311 | +puts "=" * 80 |
| 312 | +puts "" |
| 313 | + |
| 314 | +if issues.empty? && warnings.empty? |
| 315 | + puts "🎉 PERFECT! All validations passed with no issues or warnings." |
| 316 | + puts "" |
| 317 | + puts "✓ Schema parity: 100%" |
| 318 | + puts "✓ Parameter parity: 100%" |
| 319 | + puts "✓ Path parity: 100%" |
| 320 | + puts "✓ Type consistency: 100%" |
| 321 | + puts "✓ No external references" |
| 322 | + puts "✓ File structure complete" |
| 323 | + puts "" |
| 324 | + puts "mx_platform_api.yml is fully synchronized with v20111101.yaml" |
| 325 | + exit 0 |
| 326 | +elsif issues.empty? |
| 327 | + puts "✅ VALIDATION PASSED (with #{warnings.count} warnings)" |
| 328 | + puts "" |
| 329 | + puts "Critical Issues: 0" |
| 330 | + puts "Warnings: #{warnings.count}" |
| 331 | + puts "" |
| 332 | + puts "Warnings:" |
| 333 | + warnings.each { |w| puts " ⚠️ #{w}" } |
| 334 | + puts "" |
| 335 | + exit 0 |
| 336 | +else |
| 337 | + puts "❌ VALIDATION FAILED" |
| 338 | + puts "" |
| 339 | + puts "Critical Issues: #{issues.count}" |
| 340 | + puts "Warnings: #{warnings.count}" |
| 341 | + puts "" |
| 342 | + |
| 343 | + if issues.any? |
| 344 | + puts "Critical Issues:" |
| 345 | + issues.each { |i| puts " ❌ #{i}" } |
| 346 | + puts "" |
| 347 | + end |
| 348 | + |
| 349 | + if warnings.any? |
| 350 | + puts "Warnings:" |
| 351 | + warnings.each { |w| puts " ⚠️ #{w}" } |
| 352 | + puts "" |
| 353 | + end |
| 354 | + |
| 355 | + puts "Please resolve critical issues before proceeding." |
| 356 | + exit 1 |
| 357 | +end |
0 commit comments