Initial commit
This commit is contained in:
263
commands/schema-validation/.scripts/json-validator.py
Executable file
263
commands/schema-validation/.scripts/json-validator.py
Executable file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# ============================================================================
|
||||
# JSON Validator Script
|
||||
# ============================================================================
|
||||
# Purpose: Multi-backend JSON syntax validation with detailed error reporting
|
||||
# Version: 1.0.0
|
||||
# Usage: ./json-validator.py --file <path> [--verbose]
|
||||
# Returns: 0=valid, 1=invalid, 2=error
|
||||
# Backends: jq (preferred), python3 json module (fallback)
|
||||
# ============================================================================
|
||||
|
||||
import json
|
||||
import sys
|
||||
import argparse
|
||||
import subprocess
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# ====================
|
||||
# Color Support
|
||||
# ====================
|
||||
|
||||
class Colors:
|
||||
"""ANSI color codes for terminal output"""
|
||||
RED = '\033[0;31m'
|
||||
GREEN = '\033[0;32m'
|
||||
YELLOW = '\033[1;33m'
|
||||
BLUE = '\033[0;34m'
|
||||
CYAN = '\033[0;36m'
|
||||
BOLD = '\033[1m'
|
||||
NC = '\033[0m'
|
||||
|
||||
@classmethod
|
||||
def disable(cls):
|
||||
"""Disable colors for non-TTY output"""
|
||||
cls.RED = cls.GREEN = cls.YELLOW = cls.BLUE = cls.CYAN = cls.BOLD = cls.NC = ''
|
||||
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
Colors.disable()
|
||||
|
||||
|
||||
# ====================
|
||||
# Backend Detection
|
||||
# ====================
|
||||
|
||||
def detect_backend():
|
||||
"""Detect available JSON validation backend"""
|
||||
if shutil.which('jq'):
|
||||
return 'jq'
|
||||
elif sys.version_info >= (3, 0):
|
||||
return 'python3'
|
||||
else:
|
||||
return 'none'
|
||||
|
||||
|
||||
def print_backend_info():
|
||||
"""Print detected backend information"""
|
||||
backend = detect_backend()
|
||||
if backend == 'jq':
|
||||
print(f"{Colors.GREEN}✅ Backend: jq (preferred){Colors.NC}")
|
||||
elif backend == 'python3':
|
||||
print(f"{Colors.YELLOW}⚠️ Backend: python3 (fallback){Colors.NC}")
|
||||
else:
|
||||
print(f"{Colors.RED}❌ No JSON validator available{Colors.NC}")
|
||||
print(f"{Colors.BLUE}ℹ️ Install jq for better error messages: apt-get install jq{Colors.NC}")
|
||||
return backend
|
||||
|
||||
|
||||
# ====================
|
||||
# JQ Backend
|
||||
# ====================
|
||||
|
||||
def validate_with_jq(file_path, verbose=False):
|
||||
"""Validate JSON using jq (provides better error messages)"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['jq', 'empty', file_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"{Colors.GREEN}✅ Valid JSON: {file_path}{Colors.NC}")
|
||||
print(f"Backend: jq")
|
||||
return 0
|
||||
else:
|
||||
# Parse jq error message
|
||||
error_msg = result.stderr.strip()
|
||||
print(f"{Colors.RED}❌ Invalid JSON: {file_path}{Colors.NC}")
|
||||
|
||||
if verbose:
|
||||
print(f"{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.NC}")
|
||||
print(f"{Colors.RED}Error Details:{Colors.NC}")
|
||||
print(f" {error_msg}")
|
||||
print()
|
||||
print(f"{Colors.YELLOW}Remediation:{Colors.NC}")
|
||||
print(" - Check for missing commas between object properties")
|
||||
print(" - Verify bracket matching: [ ] { }")
|
||||
print(" - Ensure proper string quoting")
|
||||
print(" - Use a JSON formatter/linter in your editor")
|
||||
else:
|
||||
# Extract line number if available
|
||||
if "parse error" in error_msg.lower():
|
||||
print(f"Error: {error_msg}")
|
||||
|
||||
return 1
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"{Colors.RED}❌ File not found: {file_path}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}❌ Error running jq: {e}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
# ====================
|
||||
# Python3 Backend
|
||||
# ====================
|
||||
|
||||
def validate_with_python(file_path, verbose=False):
|
||||
"""Validate JSON using Python's json module (universal fallback)"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Attempt to parse JSON
|
||||
json.loads(content)
|
||||
|
||||
print(f"{Colors.GREEN}✅ Valid JSON: {file_path}{Colors.NC}")
|
||||
print(f"Backend: python3")
|
||||
return 0
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"{Colors.RED}❌ File not found: {file_path}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"{Colors.RED}❌ Invalid JSON: {file_path}{Colors.NC}")
|
||||
|
||||
if verbose:
|
||||
print(f"{Colors.BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Colors.NC}")
|
||||
print(f"{Colors.RED}Error Details:{Colors.NC}")
|
||||
print(f" Line: {e.lineno}")
|
||||
print(f" Column: {e.colno}")
|
||||
print(f" Issue: {e.msg}")
|
||||
print()
|
||||
|
||||
# Show problematic section
|
||||
try:
|
||||
lines = content.split('\n')
|
||||
start = max(0, e.lineno - 3)
|
||||
end = min(len(lines), e.lineno + 2)
|
||||
|
||||
print(f"Problematic Section (lines {start+1}-{end}):")
|
||||
for i in range(start, end):
|
||||
line_num = i + 1
|
||||
marker = "→" if line_num == e.lineno else " "
|
||||
print(f" {marker} {line_num:3d} | {lines[i]}")
|
||||
|
||||
print()
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"{Colors.YELLOW}Remediation:{Colors.NC}")
|
||||
print(" - Check for missing commas between array elements or object properties")
|
||||
print(" - Verify bracket matching: [ ] { }")
|
||||
print(" - Ensure all strings are properly quoted")
|
||||
print(" - Use a JSON formatter/linter in your editor")
|
||||
else:
|
||||
print(f"Error: {e.msg} at line {e.lineno}, column {e.colno}")
|
||||
|
||||
return 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}❌ Error reading file: {e}{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
# ====================
|
||||
# Main Validation
|
||||
# ====================
|
||||
|
||||
def validate_json(file_path, verbose=False):
|
||||
"""Main validation function with backend selection"""
|
||||
backend = detect_backend()
|
||||
|
||||
if backend == 'none':
|
||||
print(f"{Colors.RED}❌ No JSON validation backend available{Colors.NC}", file=sys.stderr)
|
||||
print(f"{Colors.BLUE}ℹ️ Install jq or ensure python3 is available{Colors.NC}", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if backend == 'jq':
|
||||
return validate_with_jq(file_path, verbose)
|
||||
else:
|
||||
return validate_with_python(file_path, verbose)
|
||||
|
||||
|
||||
# ====================
|
||||
# CLI Interface
|
||||
# ====================
|
||||
|
||||
def main():
|
||||
"""CLI entry point"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate JSON file syntax with multi-backend support',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
Examples:
|
||||
./json-validator.py --file plugin.json
|
||||
./json-validator.py --file marketplace.json --verbose
|
||||
./json-validator.py --detect
|
||||
|
||||
Backends:
|
||||
- jq (preferred): Fast, excellent error messages
|
||||
- python3 (fallback): Universal availability
|
||||
|
||||
Exit codes:
|
||||
0: Valid JSON
|
||||
1: Invalid JSON
|
||||
2: File error or backend unavailable
|
||||
'''
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--file',
|
||||
type=str,
|
||||
help='Path to JSON file to validate'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
help='Show detailed error information and remediation'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--detect',
|
||||
action='store_true',
|
||||
help='Detect and display available backend'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Handle backend detection
|
||||
if args.detect:
|
||||
print_backend_info()
|
||||
return 0
|
||||
|
||||
# Validate required arguments
|
||||
if not args.file:
|
||||
parser.print_help()
|
||||
return 2
|
||||
|
||||
# Perform validation
|
||||
return validate_json(args.file, args.verbose)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user