diff --git a/.gitignore b/.gitignore index 0815d1e..4765c60 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,7 @@ Thumbs.db vitest-ctrf/ playwright-report/ ctrf/ -test-results/ \ No newline at end of file +test-results/ + +# Claude Code +.claude/ \ No newline at end of file diff --git a/README.md b/README.md index cf400ff..2350d39 100644 --- a/README.md +++ b/README.md @@ -222,24 +222,24 @@ fi ``` src/ ├── components/ # React UI components -│ ├── ConfigForm.jsx # API endpoint configuration form -│ ├── TestRunner.jsx # Test execution controller -│ ├── ResultsPanel.jsx # Basic validation results display -│ └── EnhancedResultsPanel.jsx # Advanced results with export features +│ ├── ConfigForm.tsx # API endpoint configuration form +│ ├── TestRunner.tsx # Test execution controller +│ ├── ResultsPanel.tsx # Basic validation results display +│ └── EnhancedResultsPanel.tsx # Advanced results with export features ├── validators/ # JSON:API validation logic -│ ├── DocumentValidator.js # Document structure validation -│ ├── ResourceValidator.js # Resource object validation -│ ├── ErrorValidator.js # Error response validation +│ ├── DocumentValidator.ts # Document structure validation +│ ├── ResourceValidator.ts # Resource object validation +│ ├── ErrorValidator.ts # Error response validation │ ├── QueryValidator.ts # Query parameter validation -│ ├── PaginationValidator.js # Pagination validation +│ ├── PaginationValidator.ts # Pagination validation │ └── [8 more validators...] # Comprehensive validation suite ├── utils/ # Core utilities -│ ├── ValidationService.js # Main validation orchestration -│ ├── ValidationReporter.js # Report formatting and export -│ ├── ApiClient.js # HTTP request client -│ └── UrlValidator.js # URL validation utilities -├── App.jsx # Main application component -└── main.jsx # Application entry point +│ ├── ValidationService.ts # Main validation orchestration +│ ├── ValidationReporter.ts # Report formatting and export +│ ├── ApiClient.ts # HTTP request client +│ └── UrlValidator.ts # URL validation utilities +├── App.tsx # Main application component +└── main.tsx # Application entry point ``` ### Mock Server for Testing diff --git a/eslint.config.js b/eslint.config.js index df51511..d2625e7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,6 +2,7 @@ import js from '@eslint/js' import react from 'eslint-plugin-react' import reactHooks from 'eslint-plugin-react-hooks' import globals from 'globals' +import tseslint from 'typescript-eslint' export default [ { ignores: ['dist'] }, @@ -30,9 +31,34 @@ export default [ 'react/prop-types': 'off', }, }, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + parser: tseslint.parser, + parserOptions: { + project: true, + }, + globals: globals.browser, + }, + settings: { react: { version: '18.2' } }, + plugins: { + '@typescript-eslint': tseslint.plugin, + react, + 'react-hooks': reactHooks, + }, + rules: { + ...js.configs.recommended.rules, + ...tseslint.configs.recommended.at(-1).rules, + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + ...reactHooks.configs.recommended.rules, + 'react/jsx-no-target-blank': 'off', + 'react/prop-types': 'off', + }, + }, // Test files configuration { - files: ['**/*.test.{js,jsx}', '**/*.spec.{js,jsx}', 'src/test-setup.js', 'tests/**/*.js'], + files: ['**/*.test.{js,jsx,ts,tsx}', '**/*.spec.{js,jsx,ts,tsx}', 'src/test-setup.js', 'tests/**/*.{js,ts}'], languageOptions: { globals: { ...globals.browser, @@ -49,4 +75,4 @@ export default [ } } } -] \ No newline at end of file +] diff --git a/package-lock.json b/package-lock.json index 3c39e5e..38c78c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "playwright-ctrf-json-reporter": "^0.0.23", "tsx": "^4.20.6", "typescript": "^5.9.3", + "typescript-eslint": "^8.60.0", "vite": "^7.1.5", "vitest": "^3.2.4" } @@ -1005,11 +1006,10 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, - "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -1037,11 +1037,10 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2594,6 +2593,285 @@ "@types/react": "^19.2.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", + "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/type-utils": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", + "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", + "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "dev": true, + "dependencies": { + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", + "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.2.tgz", @@ -8637,6 +8915,18 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -8783,6 +9073,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", + "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.0", + "@typescript-eslint/parser": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/uint8array-extras": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", diff --git a/package.json b/package.json index 1300694..fb78cac 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "vite build", "build:cli": "swc cli.ts -o cli.js", "preview": "vite preview", - "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint src --ext js,jsx,ts,tsx --report-unused-disable-directives --max-warnings 0", "mock-server": "node mock-server/server.js", "dev:full": "concurrently \"npm run dev\" \"npm run mock-server\"", "start": "npm run dev:full", @@ -62,6 +62,7 @@ "playwright-ctrf-json-reporter": "^0.0.23", "tsx": "^4.20.6", "typescript": "^5.9.3", + "typescript-eslint": "^8.60.0", "vite": "^7.1.5", "vitest": "^3.2.4" } diff --git a/src/utils/ValidationService.ts b/src/utils/ValidationService.ts index 203abb0..f224b4f 100644 --- a/src/utils/ValidationService.ts +++ b/src/utils/ValidationService.ts @@ -15,7 +15,7 @@ import { validateJsonApiObjectExtended } from '../validators/JsonApiObjectValida import { validateContentNegotiation } from '../validators/ContentNegotiationValidator.js' import { validateUrlStructure } from '../validators/UrlStructureValidator.js' import { createComprehensiveReport } from '../utils/ValidationReporter.js' -import type { ValidationTest, ValidationReport, JsonApiDocument } from '../types/validation.js' +import type { ValidationTest, ValidationReport, JsonApiDocument, TestConfig } from '../types/validation.js' /** * Extended test config with optional fields for compatibility @@ -142,7 +142,7 @@ export async function runValidation(config: ExtendedTestConfig): Promise { + requestValidation.errors.forEach((error: { test: string; message: string }) => { results.details.push({ test: error.test, status: 'failed', @@ -150,7 +150,7 @@ export async function runValidation(config: ExtendedTestConfig): Promise { + requestValidation.warnings.forEach((warning: { test: string; message: string }) => { results.details.push({ test: warning.test, status: 'warning', @@ -175,7 +175,7 @@ export async function runValidation(config: ExtendedTestConfig): Promise { + fieldsetValidation.errors.forEach((error: { test: string; message: string }) => { results.details.push({ test: error.test, status: 'failed', @@ -422,7 +422,7 @@ export async function runValidation(config: ExtendedTestConfig): Promise { + fieldsetValidation.warnings.forEach((warning: { test: string; message: string }) => { results.details.push({ test: warning.test, status: 'warning', @@ -444,11 +444,11 @@ export async function runValidation(config: ExtendedTestConfig): Promise d.status !== 'skipped') as ValidationTest[] + const validDetails = paginationValidation.details.filter((d: { status: string }) => d.status !== 'skipped') as ValidationTest[] results.details.push(...validDetails) // Add any errors - paginationValidation.errors.forEach((error: any) => { + paginationValidation.errors.forEach((error: { test: string; message: string }) => { results.details.push({ test: error.test, status: 'failed', @@ -458,7 +458,7 @@ export async function runValidation(config: ExtendedTestConfig): Promise { + paginationValidation.warnings.forEach((warning: { test: string; message: string }) => { results.details.push({ test: warning.test, status: 'warning', diff --git a/src/validators/DocumentValidator.ts b/src/validators/DocumentValidator.ts index b40cc6e..1d9678a 100644 --- a/src/validators/DocumentValidator.ts +++ b/src/validators/DocumentValidator.ts @@ -48,6 +48,17 @@ interface LinkObject { type Link = string | LinkObject | null +interface RelationshipData { + type: string + id: string +} + +interface RelationshipObject { + data?: RelationshipData | RelationshipData[] | null + links?: Record + meta?: Record +} + /** * Validates a JSON:API document's top-level structure * @param response - The response object to validate @@ -137,7 +148,7 @@ export function validateDocument(response: unknown): ValidationResult { // Step 4b: Validate errors structure if present if (hasErrors) { - const errorsValidation = validateErrorsMember(doc.errors) as any + const errorsValidation = validateErrorsMember(doc.errors) as ValidationResult results.details.push(...errorsValidation.details) if (!errorsValidation.valid) { results.valid = false @@ -278,7 +289,7 @@ function validateDataMember(data: JsonApiResource | JsonApiResource[] | null | u }) } else { // Validate resource collection using comprehensive ResourceValidator - const collectionValidation = validateResourceCollection(data, { context: 'data' } as any) as ValidationResult + const collectionValidation = validateResourceCollection(data, { context: 'data' }) as ValidationResult results.details.push(...collectionValidation.details) if (!collectionValidation.valid) { results.valid = false @@ -290,7 +301,7 @@ function validateDataMember(data: JsonApiResource | JsonApiResource[] | null | u } } else if (typeof data === 'object') { // Single resource object - use comprehensive ResourceValidator - const resourceValidation = validateResourceObject(data, { context: 'data' } as any) as ValidationResult + const resourceValidation = validateResourceObject(data, { context: 'data' }) as ValidationResult results.details.push(...resourceValidation.details) if (!resourceValidation.valid) { results.valid = false @@ -340,7 +351,7 @@ function validateIncludedMember(included: unknown): ValidationResult { } // Validate each resource in included array using comprehensive ResourceValidator - const collectionValidation = validateResourceCollection(included as JsonApiResource[], { context: 'included' } as any) as ValidationResult + const collectionValidation = validateResourceCollection(included as JsonApiResource[], { context: 'included' }) as ValidationResult results.details.push(...collectionValidation.details) if (!collectionValidation.valid) { results.valid = false @@ -1111,10 +1122,10 @@ function extractReferencedResources(data: JsonApiResource | JsonApiResource[] | resources.forEach(resource => { if (resource && typeof resource === 'object' && resource.relationships) { - Object.values(resource.relationships).forEach((relationship: any) => { + Object.values(resource.relationships as Record).forEach((relationship) => { if (relationship && relationship.data) { const relData = Array.isArray(relationship.data) ? relationship.data : [relationship.data] - relData.forEach((rel: any) => { + relData.forEach((rel) => { if (rel && rel.type && rel.id) { references.add(`${rel.type}:${rel.id}`) } @@ -1190,10 +1201,10 @@ function analyzeRelationshipStructure(allResources: Map for (const [resourceKey, resource] of allResources) { if (resource.relationships) { - Object.values(resource.relationships).forEach((relationship: any) => { + Object.values(resource.relationships as Record).forEach((relationship) => { if (relationship && relationship.data) { const relData = Array.isArray(relationship.data) ? relationship.data : [relationship.data] - relData.forEach((rel: any) => { + relData.forEach((rel) => { if (rel && rel.type && rel.id) { const relKey = `${rel.type}:${rel.id}` const relationshipPair = `${resourceKey}->${relKey}` diff --git a/src/validators/ErrorValidator.ts b/src/validators/ErrorValidator.ts index 9baa6de..b6f8367 100644 --- a/src/validators/ErrorValidator.ts +++ b/src/validators/ErrorValidator.ts @@ -437,7 +437,7 @@ function validateErrorLinkValue(link: unknown, context: string): ValidationResul // Validate meta member names follow JSON:API naming conventions const metaKeys = Object.keys(linkObject.meta as Record) for (const metaName of metaKeys) { - const nameValidation = validateMemberName(metaName, `${context}.meta.${metaName}`) as any + const nameValidation = validateMemberName(metaName, `${context}.meta.${metaName}`) as ValidationResult results.details.push(...nameValidation.details) if (!nameValidation.valid) { results.valid = false @@ -858,7 +858,7 @@ function validateErrorMetaMember(meta: unknown, context: string): ValidationResu // Validate each meta member name follows JSON:API naming conventions for (const metaName of metaKeys) { - const nameValidation = validateMemberName(metaName, `${context}.meta.${metaName}`) as any + const nameValidation = validateMemberName(metaName, `${context}.meta.${metaName}`) as ValidationResult results.details.push(...nameValidation.details) if (!nameValidation.valid) { results.valid = false