| #!/usr/bin/env python3 | |
| """JSON Entry Validator | |
| Validates JSON entries based on content structure. | |
| Validation rules based on JSON content: | |
| - {"custom_nodes": [...]}: Validates required fields (author, title, reference, files, install_type, description) | |
| - {"models": [...]}: Validates JSON syntax only (no required fields) | |
| - Other JSON structures: Validates JSON syntax only | |
| Git repository URL validation (for custom_nodes): | |
| 1. URLs must NOT end with .git | |
| 2. URLs must follow format: https://github.com/{author}/{reponame} | |
| 3. .py and .js files are exempt from this check | |
| Supported formats: | |
| - Array format: [{...}, {...}] | |
| - Object format: {"custom_nodes": [...]} or {"models": [...]} | |
| """ | |
| import json | |
| import re | |
| import sys | |
| from pathlib import Path | |
| from typing import Dict, List, Tuple | |
| # Required fields for each entry type | |
| REQUIRED_FIELDS_CUSTOM_NODE = ['author', 'title', 'reference', 'files', 'install_type', 'description'] | |
| REQUIRED_FIELDS_MODEL = [] # model-list.json doesn't require field validation | |
| # Pattern for valid GitHub repository URL (without .git suffix) | |
| GITHUB_REPO_PATTERN = re.compile(r'^https://github\.com/[^/]+/[^/]+$') | |
| def get_entry_context(entry: Dict) -> str: | |
| """Get identifying information from entry for error messages | |
| Args: | |
| entry: JSON entry | |
| Returns: | |
| String with author and reference info | |
| """ | |
| parts = [] | |
| if 'author' in entry: | |
| parts.append(f"author={entry['author']}") | |
| if 'reference' in entry: | |
| parts.append(f"ref={entry['reference']}") | |
| if 'title' in entry: | |
| parts.append(f"title={entry['title']}") | |
| if parts: | |
| return " | ".join(parts) | |
| else: | |
| # No identifying info - show actual entry content (truncated) | |
| import json | |
| entry_str = json.dumps(entry, ensure_ascii=False) | |
| if len(entry_str) > 100: | |
| entry_str = entry_str[:100] + "..." | |
| return f"content={entry_str}" | |
| def validate_required_fields(entry: Dict, entry_index: int, required_fields: List[str]) -> List[str]: | |
| """Validate that all required fields are present | |
| Args: | |
| entry: JSON entry to validate | |
| entry_index: Index of entry in array (for error reporting) | |
| required_fields: List of required field names | |
| Returns: | |
| List of error descriptions (without entry prefix/context) | |
| """ | |
| errors = [] | |
| for field in required_fields: | |
| if field not in entry: | |
| errors.append(f"Missing required field '{field}'") | |
| elif entry[field] is None: | |
| errors.append(f"Field '{field}' is null") | |
| elif isinstance(entry[field], str) and not entry[field].strip(): | |
| errors.append(f"Field '{field}' is empty") | |
| elif field == 'files' and not entry[field]: # Empty array | |
| errors.append("Field 'files' is empty array") | |
| return errors | |
| def validate_git_repo_urls(entry: Dict, entry_index: int) -> List[str]: | |
| """Validate git repository URLs in 'files' array | |
| Requirements: | |
| - Git repo URLs must NOT end with .git | |
| - Must follow format: https://github.com/{author}/{reponame} | |
| - .py and .js files are exempt | |
| Args: | |
| entry: JSON entry to validate | |
| entry_index: Index of entry in array (for error reporting) | |
| Returns: | |
| List of error descriptions (without entry prefix/context) | |
| """ | |
| errors = [] | |
| if 'files' not in entry or not isinstance(entry['files'], list): | |
| return errors | |
| for file_url in entry['files']: | |
| if not isinstance(file_url, str): | |
| continue | |
| # Skip .py and .js files - they're exempt from git repo validation | |
| if file_url.endswith('.py') or file_url.endswith('.js'): | |
| continue | |
| # Check if it's a GitHub URL (likely a git repo) | |
| if 'github.com' in file_url: | |
| # Error if URL ends with .git | |
| if file_url.endswith('.git'): | |
| errors.append(f"Git repo URL must NOT end with .git: {file_url}") | |
| continue | |
| # Validate format: https://github.com/{author}/{reponame} | |
| if not GITHUB_REPO_PATTERN.match(file_url): | |
| errors.append(f"Invalid git repo URL format (expected https://github.com/author/reponame): {file_url}") | |
| return errors | |
| def validate_entry(entry: Dict, entry_index: int, required_fields: List[str]) -> List[str]: | |
| """Validate a single JSON entry | |
| Args: | |
| entry: JSON entry to validate | |
| entry_index: Index of entry in array (for error reporting) | |
| required_fields: List of required field names | |
| Returns: | |
| List of error messages (empty if valid) | |
| """ | |
| errors = [] | |
| # Check required fields | |
| errors.extend(validate_required_fields(entry, entry_index, required_fields)) | |
| # Check git repository URLs | |
| errors.extend(validate_git_repo_urls(entry, entry_index)) | |
| return errors | |
| def validate_json_file(file_path: str) -> Tuple[bool, List[str]]: | |
| """Validate JSON file containing entries | |
| Args: | |
| file_path: Path to JSON file | |
| Returns: | |
| Tuple of (is_valid, error_messages) | |
| """ | |
| errors = [] | |
| # Check file exists | |
| path = Path(file_path) | |
| if not path.exists(): | |
| return False, [f"File not found: {file_path}"] | |
| # Load JSON | |
| try: | |
| with open(path, 'r', encoding='utf-8') as f: | |
| data = json.load(f) | |
| except json.JSONDecodeError as e: | |
| return False, [f"Invalid JSON: {e}"] | |
| except Exception as e: | |
| return False, [f"Error reading file: {e}"] | |
| # Determine required fields based on JSON content | |
| required_fields = [] | |
| # Validate structure - support both array and object formats | |
| entries_to_validate = [] | |
| if isinstance(data, list): | |
| # Direct array format: [{...}, {...}] | |
| entries_to_validate = data | |
| elif isinstance(data, dict): | |
| # Object format: {"custom_nodes": [...]} or {"models": [...]} | |
| # Determine validation based on keys | |
| if 'custom_nodes' in data and isinstance(data['custom_nodes'], list): | |
| required_fields = REQUIRED_FIELDS_CUSTOM_NODE | |
| entries_to_validate = data['custom_nodes'] | |
| elif 'models' in data and isinstance(data['models'], list): | |
| required_fields = REQUIRED_FIELDS_MODEL | |
| entries_to_validate = data['models'] | |
| else: | |
| # Other JSON structures (extension-node-map.json, etc.) - just validate JSON syntax | |
| return True, [] | |
| else: | |
| return False, ["JSON root must be either an array or an object containing arrays"] | |
| # Validate each entry | |
| for idx, entry in enumerate(entries_to_validate, start=1): | |
| if not isinstance(entry, dict): | |
| # Show actual value for type errors | |
| entry_str = json.dumps(entry, ensure_ascii=False) if not isinstance(entry, str) else repr(entry) | |
| if len(entry_str) > 150: | |
| entry_str = entry_str[:150] + "..." | |
| errors.append(f"\n❌ Entry #{idx}: Must be an object, got {type(entry).__name__}") | |
| errors.append(f" Actual value: {entry_str}") | |
| continue | |
| entry_errors = validate_entry(entry, idx, required_fields) | |
| if entry_errors: | |
| # Group errors by entry with context | |
| context = get_entry_context(entry) | |
| errors.append(f"\n❌ Entry #{idx} ({context}):") | |
| for error in entry_errors: | |
| errors.append(f" - {error}") | |
| is_valid = len(errors) == 0 | |
| return is_valid, errors | |
| def main(): | |
| """Main entry point""" | |
| if len(sys.argv) < 2: | |
| print("Usage: python json-checker.py <json-file>") | |
| print("\nValidates JSON entries based on content:") | |
| print(" - {\"custom_nodes\": [...]}: Validates required fields (author, title, reference, files, install_type, description)") | |
| print(" - {\"models\": [...]}: Validates JSON syntax only (no required fields)") | |
| print(" - Other JSON structures: Validates JSON syntax only") | |
| print("\nGit repo URL validation (for custom_nodes):") | |
| print(" - URLs must NOT end with .git") | |
| print(" - URLs must follow: https://github.com/{author}/{reponame}") | |
| sys.exit(1) | |
| file_path = sys.argv[1] | |
| is_valid, errors = validate_json_file(file_path) | |
| if is_valid: | |
| print(f"✅ {file_path}: Validation passed") | |
| sys.exit(0) | |
| else: | |
| print(f"Validating: {file_path}") | |
| print("=" * 60) | |
| print("❌ Validation failed!\n") | |
| print("Errors:") | |
| # Count actual errors (lines starting with " -") | |
| error_count = sum(1 for e in errors if e.strip().startswith('-')) | |
| for error in errors: | |
| # Don't add ❌ prefix to grouped entries (they already have it) | |
| if error.strip().startswith('❌'): | |
| print(error) | |
| else: | |
| print(error) | |
| print(f"\nTotal errors: {error_count}") | |
| sys.exit(1) | |
| if __name__ == '__main__': | |
| main() | |
Xet Storage Details
- Size:
- 8.99 kB
- Xet hash:
- 4e920d7a858a7b08e280633ab884886303f08329a945aac2ad9c69fc7d87895c
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.