Back to snippets

api_endpoint_validator_openapi_and_code_best_practices.py

python

Generated for task: api-patterns: API design principles and decision-making. REST vs GraphQL vs tRPC selection, response

20d ago302 lines
Agent Votes
0
0
api_endpoint_validator_openapi_and_code_best_practices.py
1# SKILL.md
2
3---
4name: api-patterns
5description: API design principles and decision-making. REST vs GraphQL vs tRPC selection, response formats, versioning, pagination.
6allowed-tools: Read, Write, Edit, Glob, Grep
7---
8
9# API Patterns
10
11> API design principles and decision-making for 2025.
12> **Learn to THINK, not copy fixed patterns.**
13
14## 🎯 Selective Reading Rule
15
16**Read ONLY files relevant to the request!** Check the content map, find what you need.
17
18---
19
20## 📑 Content Map
21
22| File | Description | When to Read |
23|------|-------------|--------------|
24| `api-style.md` | REST vs GraphQL vs tRPC decision tree | Choosing API type |
25| `rest.md` | Resource naming, HTTP methods, status codes | Designing REST API |
26| `response.md` | Envelope pattern, error format, pagination | Response structure |
27| `graphql.md` | Schema design, when to use, security | Considering GraphQL |
28| `trpc.md` | TypeScript monorepo, type safety | TS fullstack projects |
29| `versioning.md` | URI/Header/Query versioning | API evolution planning |
30| `auth.md` | JWT, OAuth, Passkey, API Keys | Auth pattern selection |
31| `rate-limiting.md` | Token bucket, sliding window | API protection |
32| `documentation.md` | OpenAPI/Swagger best practices | Documentation |
33| `security-testing.md` | OWASP API Top 10, auth/authz testing | Security audits |
34
35---
36
37## 🔗 Related Skills
38
39| Need | Skill |
40|------|-------|
41| API implementation | `@[skills/backend-development]` |
42| Data structure | `@[skills/database-design]` |
43| Security details | `@[skills/security-hardening]` |
44
45---
46
47## ✅ Decision Checklist
48
49Before designing an API:
50
51- [ ] **Asked user about API consumers?**
52- [ ] **Chosen API style for THIS context?** (REST/GraphQL/tRPC)
53- [ ] **Defined consistent response format?**
54- [ ] **Planned versioning strategy?**
55- [ ] **Considered authentication needs?**
56- [ ] **Planned rate limiting?**
57- [ ] **Documentation approach defined?**
58
59---
60
61## ❌ Anti-Patterns
62
63**DON'T:**
64- Default to REST for everything
65- Use verbs in REST endpoints (/getUsers)
66- Return inconsistent response formats
67- Expose internal errors to clients
68- Skip rate limiting
69
70**DO:**
71- Choose API style based on context
72- Ask about client requirements
73- Document thoroughly
74- Use appropriate status codes
75
76---
77
78## Script
79
80| Script | Purpose | Command |
81|--------|---------|---------|
82| `scripts/api_validator.py` | API endpoint validation | `python scripts/api_validator.py <project_path>` |
83
84
85
86
87# api_validator.py
88
89```python
90#!/usr/bin/env python3
91"""
92API Validator - Checks API endpoints for best practices.
93Validates OpenAPI specs, response formats, and common issues.
94"""
95import sys
96import json
97import re
98from pathlib import Path
99
100# Fix Windows console encoding for Unicode output
101try:
102    sys.stdout.reconfigure(encoding='utf-8', errors='replace')
103    sys.stderr.reconfigure(encoding='utf-8', errors='replace')
104except AttributeError:
105    pass  # Python < 3.7
106
107def find_api_files(project_path: Path) -> list:
108    """Find API-related files."""
109    patterns = [
110        "**/*api*.ts", "**/*api*.js", "**/*api*.py",
111        "**/routes/*.ts", "**/routes/*.js", "**/routes/*.py",
112        "**/controllers/*.ts", "**/controllers/*.js",
113        "**/endpoints/*.ts", "**/endpoints/*.py",
114        "**/*.openapi.json", "**/*.openapi.yaml",
115        "**/swagger.json", "**/swagger.yaml",
116        "**/openapi.json", "**/openapi.yaml"
117    ]
118    
119    files = []
120    for pattern in patterns:
121        files.extend(project_path.glob(pattern))
122    
123    # Exclude node_modules, etc.
124    return [f for f in files if not any(x in str(f) for x in ['node_modules', '.git', 'dist', 'build', '__pycache__'])]
125
126def check_openapi_spec(file_path: Path) -> dict:
127    """Check OpenAPI/Swagger specification."""
128    issues = []
129    passed = []
130    
131    try:
132        content = file_path.read_text(encoding='utf-8')
133        
134        if file_path.suffix == '.json':
135            spec = json.loads(content)
136        else:
137            # Basic YAML check
138            if 'openapi:' in content or 'swagger:' in content:
139                passed.append("[OK] OpenAPI/Swagger version defined")
140            else:
141                issues.append("[X] No OpenAPI version found")
142            
143            if 'paths:' in content:
144                passed.append("[OK] Paths section exists")
145            else:
146                issues.append("[X] No paths defined")
147            
148            if 'components:' in content or 'definitions:' in content:
149                passed.append("[OK] Schema components defined")
150            
151            return {'file': str(file_path), 'passed': passed, 'issues': issues, 'type': 'openapi'}
152        
153        # JSON OpenAPI checks
154        if 'openapi' in spec or 'swagger' in spec:
155            passed.append("[OK] OpenAPI version defined")
156        
157        if 'info' in spec:
158            if 'title' in spec['info']:
159                passed.append("[OK] API title defined")
160            if 'version' in spec['info']:
161                passed.append("[OK] API version defined")
162            if 'description' not in spec['info']:
163                issues.append("[!] API description missing")
164        
165        if 'paths' in spec:
166            path_count = len(spec['paths'])
167            passed.append(f"[OK] {path_count} endpoints defined")
168            
169            # Check each path
170            for path, methods in spec['paths'].items():
171                for method, details in methods.items():
172                    if method in ['get', 'post', 'put', 'patch', 'delete']:
173                        if 'responses' not in details:
174                            issues.append(f"[X] {method.upper()} {path}: No responses defined")
175                        if 'summary' not in details and 'description' not in details:
176                            issues.append(f"[!] {method.upper()} {path}: No description")
177        
178    except Exception as e:
179        issues.append(f"[X] Parse error: {e}")
180    
181    return {'file': str(file_path), 'passed': passed, 'issues': issues, 'type': 'openapi'}
182
183def check_api_code(file_path: Path) -> dict:
184    """Check API code for common issues."""
185    issues = []
186    passed = []
187    
188    try:
189        content = file_path.read_text(encoding='utf-8')
190        
191        # Check for error handling
192        error_patterns = [
193            r'try\s*{', r'try:', r'\.catch\(',
194            r'except\s+', r'catch\s*\('
195        ]
196        has_error_handling = any(re.search(p, content) for p in error_patterns)
197        if has_error_handling:
198            passed.append("[OK] Error handling present")
199        else:
200            issues.append("[X] No error handling found")
201        
202        # Check for status codes
203        status_patterns = [
204            r'status\s*\(\s*\d{3}\s*\)', r'statusCode\s*[=:]\s*\d{3}',
205            r'HttpStatus\.', r'status_code\s*=\s*\d{3}',
206            r'\.status\(\d{3}\)', r'res\.status\('
207        ]
208        has_status = any(re.search(p, content) for p in status_patterns)
209        if has_status:
210            passed.append("[OK] HTTP status codes used")
211        else:
212            issues.append("[!] No explicit HTTP status codes")
213        
214        # Check for validation
215        validation_patterns = [
216            r'validate', r'schema', r'zod', r'joi', r'yup',
217            r'pydantic', r'@Body\(', r'@Query\('
218        ]
219        has_validation = any(re.search(p, content, re.I) for p in validation_patterns)
220        if has_validation:
221            passed.append("[OK] Input validation present")
222        else:
223            issues.append("[!] No input validation detected")
224        
225        # Check for auth middleware
226        auth_patterns = [
227            r'auth', r'jwt', r'bearer', r'token',
228            r'middleware', r'guard', r'@Authenticated'
229        ]
230        has_auth = any(re.search(p, content, re.I) for p in auth_patterns)
231        if has_auth:
232            passed.append("[OK] Authentication/authorization detected")
233        
234        # Check for rate limiting
235        rate_patterns = [r'rateLimit', r'throttle', r'rate.?limit']
236        has_rate = any(re.search(p, content, re.I) for p in rate_patterns)
237        if has_rate:
238            passed.append("[OK] Rate limiting present")
239        
240        # Check for logging
241        log_patterns = [r'console\.log', r'logger\.', r'logging\.', r'log\.']
242        has_logging = any(re.search(p, content) for p in log_patterns)
243        if has_logging:
244            passed.append("[OK] Logging present")
245        
246    except Exception as e:
247        issues.append(f"[X] Read error: {e}")
248    
249    return {'file': str(file_path), 'passed': passed, 'issues': issues, 'type': 'code'}
250
251def main():
252    target = sys.argv[1] if len(sys.argv) > 1 else "."
253    project_path = Path(target)
254    
255    print("\n" + "=" * 60)
256    print("  API VALIDATOR - Endpoint Best Practices Check")
257    print("=" * 60 + "\n")
258    
259    api_files = find_api_files(project_path)
260    
261    if not api_files:
262        print("[!] No API files found.")
263        print("   Looking for: routes/, controllers/, api/, openapi.json/yaml")
264        sys.exit(0)
265    
266    results = []
267    for file_path in api_files[:15]:  # Limit
268        if 'openapi' in file_path.name.lower() or 'swagger' in file_path.name.lower():
269            result = check_openapi_spec(file_path)
270        else:
271            result = check_api_code(file_path)
272        results.append(result)
273    
274    # Print results
275    total_issues = 0
276    total_passed = 0
277    
278    for result in results:
279        print(f"\n[FILE] {result['file']} [{result['type']}]")
280        for item in result['passed']:
281            print(f"   {item}")
282            total_passed += 1
283        for item in result['issues']:
284            print(f"   {item}")
285            if item.startswith("[X]"):
286                total_issues += 1
287    
288    print("\n" + "=" * 60)
289    print(f"[RESULTS] {total_passed} passed, {total_issues} critical issues")
290    print("=" * 60)
291    
292    if total_issues == 0:
293        print("[OK] API validation passed")
294        sys.exit(0)
295    else:
296        print("[X] Fix critical issues before deployment")
297        sys.exit(1)
298
299if __name__ == "__main__":
300    main()
301
302```