@agentic-qe/v3 3.0.0-alpha.6 → 3.0.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agents/v3/subagents/v3-qe-code-reviewer.md +339 -0
- package/assets/agents/v3/subagents/v3-qe-integration-reviewer.md +344 -0
- package/assets/agents/v3/subagents/v3-qe-performance-reviewer.md +351 -0
- package/assets/agents/v3/subagents/v3-qe-security-reviewer.md +374 -0
- package/assets/agents/v3/subagents/v3-qe-tdd-green.md +334 -0
- package/assets/agents/v3/subagents/v3-qe-tdd-red.md +329 -0
- package/assets/agents/v3/subagents/v3-qe-tdd-refactor.md +361 -0
- package/assets/agents/v3/v3-qe-accessibility-auditor.md +266 -0
- package/assets/agents/v3/v3-qe-bdd-generator.md +279 -0
- package/assets/agents/v3/v3-qe-chaos-engineer.md +265 -0
- package/assets/agents/v3/v3-qe-code-complexity.md +298 -0
- package/assets/agents/v3/v3-qe-code-intelligence.md +262 -0
- package/assets/agents/v3/v3-qe-contract-validator.md +267 -0
- package/assets/agents/v3/v3-qe-coverage-specialist.md +227 -0
- package/assets/agents/v3/v3-qe-defect-predictor.md +251 -0
- package/assets/agents/v3/v3-qe-dependency-mapper.md +277 -0
- package/assets/agents/v3/v3-qe-deployment-advisor.md +275 -0
- package/assets/agents/v3/v3-qe-flaky-hunter.md +248 -0
- package/assets/agents/v3/v3-qe-fleet-commander.md +293 -0
- package/assets/agents/v3/v3-qe-gap-detector.md +260 -0
- package/assets/agents/v3/v3-qe-graphql-tester.md +308 -0
- package/assets/agents/v3/v3-qe-impact-analyzer.md +299 -0
- package/assets/agents/v3/v3-qe-integration-tester.md +238 -0
- package/assets/agents/v3/v3-qe-kg-builder.md +273 -0
- package/assets/agents/v3/v3-qe-learning-coordinator.md +226 -0
- package/assets/agents/v3/v3-qe-load-tester.md +280 -0
- package/assets/agents/v3/v3-qe-metrics-optimizer.md +300 -0
- package/assets/agents/v3/v3-qe-mutation-tester.md +301 -0
- package/assets/agents/v3/v3-qe-parallel-executor.md +240 -0
- package/assets/agents/v3/v3-qe-pattern-learner.md +271 -0
- package/assets/agents/v3/v3-qe-performance-tester.md +262 -0
- package/assets/agents/v3/v3-qe-property-tester.md +247 -0
- package/assets/agents/v3/v3-qe-quality-gate.md +218 -0
- package/assets/agents/v3/v3-qe-queen-coordinator.md +214 -0
- package/assets/agents/v3/v3-qe-qx-partner.md +313 -0
- package/assets/agents/v3/v3-qe-regression-analyzer.md +322 -0
- package/assets/agents/v3/v3-qe-requirements-validator.md +360 -0
- package/assets/agents/v3/v3-qe-responsive-tester.md +311 -0
- package/assets/agents/v3/v3-qe-retry-handler.md +256 -0
- package/assets/agents/v3/v3-qe-risk-assessor.md +273 -0
- package/assets/agents/v3/v3-qe-root-cause-analyzer.md +286 -0
- package/assets/agents/v3/v3-qe-security-auditor.md +299 -0
- package/assets/agents/v3/v3-qe-security-scanner.md +235 -0
- package/assets/agents/v3/v3-qe-tdd-specialist.md +239 -0
- package/assets/agents/v3/v3-qe-test-architect.md +233 -0
- package/assets/agents/v3/v3-qe-transfer-specialist.md +295 -0
- package/assets/agents/v3/v3-qe-visual-tester.md +232 -0
- package/assets/skills/accessibility-testing/SKILL.md +216 -0
- package/assets/skills/agentdb-advanced/SKILL.md +550 -0
- package/assets/skills/agentdb-learning/SKILL.md +545 -0
- package/assets/skills/agentdb-memory-patterns/SKILL.md +339 -0
- package/assets/skills/agentdb-optimization/SKILL.md +509 -0
- package/assets/skills/agentdb-vector-search/SKILL.md +339 -0
- package/assets/skills/agentic-jujutsu/SKILL.md +645 -0
- package/assets/skills/agentic-quality-engineering/SKILL.md +335 -0
- package/assets/skills/api-testing-patterns/SKILL.md +294 -0
- package/assets/skills/aqe-v2-v3-migration/skill.md +322 -0
- package/assets/skills/brutal-honesty-review/README.md +218 -0
- package/assets/skills/brutal-honesty-review/SKILL.md +235 -0
- package/assets/skills/brutal-honesty-review/resources/assessment-rubrics.md +295 -0
- package/assets/skills/brutal-honesty-review/resources/review-template.md +102 -0
- package/assets/skills/brutal-honesty-review/scripts/assess-code.sh +179 -0
- package/assets/skills/brutal-honesty-review/scripts/assess-tests.sh +223 -0
- package/assets/skills/bug-reporting-excellence/SKILL.md +225 -0
- package/assets/skills/chaos-engineering-resilience/SKILL.md +158 -0
- package/assets/skills/cicd-pipeline-qe-orchestrator/README.md +304 -0
- package/assets/skills/cicd-pipeline-qe-orchestrator/SKILL.md +315 -0
- package/assets/skills/cicd-pipeline-qe-orchestrator/resources/workflows/microservice-pipeline.md +239 -0
- package/assets/skills/cicd-pipeline-qe-orchestrator/resources/workflows/mobile-pipeline.md +375 -0
- package/assets/skills/cicd-pipeline-qe-orchestrator/resources/workflows/monolith-pipeline.md +268 -0
- package/assets/skills/code-review-quality/SKILL.md +227 -0
- package/assets/skills/compatibility-testing/SKILL.md +205 -0
- package/assets/skills/compliance-testing/SKILL.md +225 -0
- package/assets/skills/consultancy-practices/SKILL.md +202 -0
- package/assets/skills/context-driven-testing/SKILL.md +196 -0
- package/assets/skills/contract-testing/SKILL.md +222 -0
- package/assets/skills/database-testing/SKILL.md +244 -0
- package/assets/skills/exploratory-testing-advanced/SKILL.md +201 -0
- package/assets/skills/flow-nexus-neural/SKILL.md +738 -0
- package/assets/skills/flow-nexus-platform/SKILL.md +1157 -0
- package/assets/skills/flow-nexus-swarm/SKILL.md +610 -0
- package/assets/skills/github-code-review/SKILL.md +1140 -0
- package/assets/skills/github-multi-repo/SKILL.md +874 -0
- package/assets/skills/github-project-management/SKILL.md +1277 -0
- package/assets/skills/github-release-management/SKILL.md +1081 -0
- package/assets/skills/github-workflow-automation/SKILL.md +1065 -0
- package/assets/skills/hive-mind-advanced/SKILL.md +712 -0
- package/assets/skills/holistic-testing-pact/SKILL.md +171 -0
- package/assets/skills/hooks-automation/SKILL.md +1201 -0
- package/assets/skills/localization-testing/SKILL.md +221 -0
- package/assets/skills/mobile-testing/SKILL.md +219 -0
- package/assets/skills/mutation-testing/SKILL.md +229 -0
- package/assets/skills/n8n-expression-testing/SKILL.md +434 -0
- package/assets/skills/n8n-integration-testing-patterns/SKILL.md +540 -0
- package/assets/skills/n8n-security-testing/SKILL.md +599 -0
- package/assets/skills/n8n-trigger-testing-strategies/SKILL.md +541 -0
- package/assets/skills/n8n-workflow-testing-fundamentals/SKILL.md +447 -0
- package/assets/skills/pair-programming/SKILL.md +1202 -0
- package/assets/skills/performance-analysis/SKILL.md +563 -0
- package/assets/skills/performance-testing/SKILL.md +310 -0
- package/assets/skills/quality-metrics/SKILL.md +225 -0
- package/assets/skills/reasoningbank-agentdb/SKILL.md +446 -0
- package/assets/skills/reasoningbank-intelligence/SKILL.md +201 -0
- package/assets/skills/refactoring-patterns/SKILL.md +205 -0
- package/assets/skills/regression-testing/SKILL.md +227 -0
- package/assets/skills/risk-based-testing/SKILL.md +206 -0
- package/assets/skills/security-testing/SKILL.md +306 -0
- package/assets/skills/sherlock-review/SKILL.md +250 -0
- package/assets/skills/shift-left-testing/SKILL.md +225 -0
- package/assets/skills/shift-right-testing/SKILL.md +227 -0
- package/assets/skills/six-thinking-hats/README.md +190 -0
- package/assets/skills/six-thinking-hats/SKILL.md +280 -0
- package/assets/skills/six-thinking-hats/resources/examples/api-testing-example.md +345 -0
- package/assets/skills/six-thinking-hats/resources/templates/solo-session-template.md +167 -0
- package/assets/skills/six-thinking-hats/resources/templates/team-session-template.md +336 -0
- package/assets/skills/skill-builder/SKILL.md +910 -0
- package/assets/skills/sparc-methodology/SKILL.md +1115 -0
- package/assets/skills/stream-chain/SKILL.md +563 -0
- package/assets/skills/swarm-advanced/SKILL.md +973 -0
- package/assets/skills/swarm-orchestration/SKILL.md +179 -0
- package/assets/skills/tdd-london-chicago/SKILL.md +244 -0
- package/assets/skills/technical-writing/SKILL.md +178 -0
- package/assets/skills/test-automation-strategy/SKILL.md +230 -0
- package/assets/skills/test-data-management/SKILL.md +270 -0
- package/assets/skills/test-design-techniques/SKILL.md +244 -0
- package/assets/skills/test-environment-management/SKILL.md +243 -0
- package/assets/skills/test-reporting-analytics/SKILL.md +214 -0
- package/assets/skills/testability-scoring/README.md +71 -0
- package/assets/skills/testability-scoring/SKILL.md +346 -0
- package/assets/skills/testability-scoring/resources/templates/config.template.js +84 -0
- package/assets/skills/testability-scoring/resources/templates/testability-scoring.spec.template.js +532 -0
- package/assets/skills/testability-scoring/scripts/generate-html-report.js +1007 -0
- package/assets/skills/testability-scoring/scripts/run-assessment.sh +70 -0
- package/assets/skills/v3-qe-chaos-resilience/SKILL.md +238 -0
- package/assets/skills/v3-qe-code-intelligence/SKILL.md +209 -0
- package/assets/skills/v3-qe-contract-testing/SKILL.md +218 -0
- package/assets/skills/v3-qe-coverage-analysis/SKILL.md +187 -0
- package/assets/skills/v3-qe-defect-intelligence/SKILL.md +205 -0
- package/assets/skills/v3-qe-learning-optimization/SKILL.md +238 -0
- package/assets/skills/v3-qe-quality-assessment/SKILL.md +213 -0
- package/assets/skills/v3-qe-requirements-validation/SKILL.md +248 -0
- package/assets/skills/v3-qe-test-execution/SKILL.md +182 -0
- package/assets/skills/v3-qe-test-generation/SKILL.md +141 -0
- package/assets/skills/v3-qe-visual-accessibility/SKILL.md +242 -0
- package/assets/skills/verification-quality/SKILL.md +649 -0
- package/assets/skills/visual-testing-advanced/SKILL.md +219 -0
- package/assets/skills/xp-practices/SKILL.md +229 -0
- package/dist/cli/bundle.js +23 -13
- package/dist/init/agents-installer.js +4 -4
- package/dist/init/agents-installer.js.map +1 -1
- package/dist/init/init-wizard.d.ts.map +1 -1
- package/dist/init/init-wizard.js +15 -5
- package/dist/init/init-wizard.js.map +1 -1
- package/dist/init/skills-installer.js +4 -4
- package/dist/init/skills-installer.js.map +1 -1
- package/package.json +7 -1
- package/docs/analysis/V3-INIT-REQUIREMENTS-ANALYSIS.md +0 -352
- package/implementation/README.md +0 -90
- package/implementation/adrs/ADR-030-coherence-gated-quality-gates.md +0 -312
- package/implementation/adrs/ADR-031-strange-loop-self-awareness.md +0 -484
- package/implementation/adrs/ADR-032-time-crystal-scheduling.md +0 -530
- package/implementation/adrs/ADR-033-early-exit-testing.md +0 -634
- package/implementation/adrs/ADR-034-neural-topology-optimizer.md +0 -589
- package/implementation/adrs/ADR-035-causal-discovery.md +0 -610
- package/implementation/adrs/ADR-036-result-persistence.md +0 -326
- package/implementation/adrs/ADR-037-v3-qe-agent-naming.md +0 -105
- package/implementation/adrs/ADR-038-v3-qe-memory-unification.md +0 -154
- package/implementation/adrs/ADR-039-v3-qe-mcp-optimization.md +0 -179
- package/implementation/adrs/ADR-040-v3-qe-agentic-flow-integration.md +0 -240
- package/implementation/adrs/ADR-041-v3-qe-cli-enhancement.md +0 -296
- package/implementation/adrs/ADR-042-v3-qe-token-tracking-integration.md +0 -517
- package/implementation/adrs/v3-adrs.md +0 -2783
- package/implementation/planning/AQE-V3-MASTER-PLAN.md +0 -815
- package/security-scan-report-2026-01-11.md +0 -410
- package/security-verification-report-2026-01-11.md +0 -278
- package/src/benchmarks/performance-benchmarks.ts +0 -646
- package/src/benchmarks/run-benchmarks.ts +0 -324
- package/src/causal-discovery/causal-graph.ts +0 -450
- package/src/causal-discovery/discovery-engine.ts +0 -438
- package/src/causal-discovery/index.ts +0 -117
- package/src/causal-discovery/types.ts +0 -456
- package/src/causal-discovery/weight-matrix.ts +0 -453
- package/src/cli/commands/qe-tools.ts +0 -634
- package/src/cli/index.ts +0 -1976
- package/src/compatibility/agent-mapper.ts +0 -291
- package/src/compatibility/cli-adapter.ts +0 -277
- package/src/compatibility/config-migrator.ts +0 -334
- package/src/compatibility/index.ts +0 -112
- package/src/compatibility/mcp-adapter.ts +0 -248
- package/src/compatibility/types.ts +0 -156
- package/src/coordination/claims/claim-repository.ts +0 -636
- package/src/coordination/claims/claim-service.ts +0 -675
- package/src/coordination/claims/handoff-manager.ts +0 -535
- package/src/coordination/claims/index.ts +0 -276
- package/src/coordination/claims/interfaces.ts +0 -687
- package/src/coordination/claims/work-stealing.ts +0 -436
- package/src/coordination/cross-domain-router.ts +0 -492
- package/src/coordination/index.ts +0 -127
- package/src/coordination/interfaces.ts +0 -691
- package/src/coordination/protocol-executor.ts +0 -760
- package/src/coordination/protocols/code-intelligence-index.ts +0 -855
- package/src/coordination/protocols/defect-investigation.ts +0 -1184
- package/src/coordination/protocols/index.ts +0 -11
- package/src/coordination/protocols/learning-consolidation.ts +0 -1181
- package/src/coordination/protocols/morning-sync.ts +0 -1055
- package/src/coordination/protocols/quality-gate.ts +0 -1566
- package/src/coordination/protocols/security-audit.ts +0 -1587
- package/src/coordination/queen-coordinator.ts +0 -1176
- package/src/coordination/result-saver.ts +0 -780
- package/src/coordination/task-executor.ts +0 -1146
- package/src/coordination/workflow-orchestrator.ts +0 -1917
- package/src/domains/chaos-resilience/coordinator.ts +0 -1032
- package/src/domains/chaos-resilience/index.ts +0 -143
- package/src/domains/chaos-resilience/interfaces.ts +0 -659
- package/src/domains/chaos-resilience/plugin.ts +0 -691
- package/src/domains/chaos-resilience/services/chaos-engineer.ts +0 -1097
- package/src/domains/chaos-resilience/services/index.ts +0 -19
- package/src/domains/chaos-resilience/services/load-tester.ts +0 -799
- package/src/domains/chaos-resilience/services/performance-profiler.ts +0 -792
- package/src/domains/code-intelligence/coordinator.ts +0 -631
- package/src/domains/code-intelligence/index.ts +0 -86
- package/src/domains/code-intelligence/interfaces.ts +0 -162
- package/src/domains/code-intelligence/plugin.ts +0 -451
- package/src/domains/code-intelligence/services/impact-analyzer.ts +0 -567
- package/src/domains/code-intelligence/services/index.ts +0 -26
- package/src/domains/code-intelligence/services/knowledge-graph.ts +0 -1067
- package/src/domains/code-intelligence/services/semantic-analyzer.ts +0 -901
- package/src/domains/contract-testing/coordinator.ts +0 -1038
- package/src/domains/contract-testing/index.ts +0 -122
- package/src/domains/contract-testing/interfaces.ts +0 -458
- package/src/domains/contract-testing/plugin.ts +0 -746
- package/src/domains/contract-testing/services/api-compatibility.ts +0 -748
- package/src/domains/contract-testing/services/contract-validator.ts +0 -1700
- package/src/domains/contract-testing/services/index.ts +0 -19
- package/src/domains/contract-testing/services/schema-validator.ts +0 -1102
- package/src/domains/coverage-analysis/coordinator.ts +0 -485
- package/src/domains/coverage-analysis/index.ts +0 -114
- package/src/domains/coverage-analysis/interfaces.ts +0 -142
- package/src/domains/coverage-analysis/plugin.ts +0 -172
- package/src/domains/coverage-analysis/services/coverage-analyzer.ts +0 -449
- package/src/domains/coverage-analysis/services/coverage-embedder.ts +0 -733
- package/src/domains/coverage-analysis/services/coverage-parser.ts +0 -753
- package/src/domains/coverage-analysis/services/gap-detector.ts +0 -592
- package/src/domains/coverage-analysis/services/hnsw-index.ts +0 -728
- package/src/domains/coverage-analysis/services/index.ts +0 -61
- package/src/domains/coverage-analysis/services/risk-scorer.ts +0 -540
- package/src/domains/coverage-analysis/services/sublinear-analyzer.ts +0 -747
- package/src/domains/defect-intelligence/coordinator.ts +0 -635
- package/src/domains/defect-intelligence/index.ts +0 -83
- package/src/domains/defect-intelligence/interfaces.ts +0 -152
- package/src/domains/defect-intelligence/plugin.ts +0 -483
- package/src/domains/defect-intelligence/services/causal-root-cause-analyzer.ts +0 -494
- package/src/domains/defect-intelligence/services/defect-predictor.ts +0 -852
- package/src/domains/defect-intelligence/services/index.ts +0 -37
- package/src/domains/defect-intelligence/services/pattern-learner.ts +0 -738
- package/src/domains/defect-intelligence/services/root-cause-analyzer.ts +0 -637
- package/src/domains/domain-interface.ts +0 -77
- package/src/domains/index.ts +0 -23
- package/src/domains/learning-optimization/coordinator.ts +0 -1215
- package/src/domains/learning-optimization/index.ts +0 -127
- package/src/domains/learning-optimization/interfaces.ts +0 -570
- package/src/domains/learning-optimization/plugin.ts +0 -851
- package/src/domains/learning-optimization/services/index.ts +0 -29
- package/src/domains/learning-optimization/services/learning-coordinator.ts +0 -972
- package/src/domains/learning-optimization/services/metrics-optimizer.ts +0 -915
- package/src/domains/learning-optimization/services/production-intel.ts +0 -971
- package/src/domains/learning-optimization/services/transfer-specialist.ts +0 -723
- package/src/domains/quality-assessment/coherence/gate-controller.ts +0 -549
- package/src/domains/quality-assessment/coherence/index.ts +0 -211
- package/src/domains/quality-assessment/coherence/lambda-calculator.ts +0 -384
- package/src/domains/quality-assessment/coherence/partition-detector.ts +0 -469
- package/src/domains/quality-assessment/coherence/types.ts +0 -384
- package/src/domains/quality-assessment/coordinator.ts +0 -605
- package/src/domains/quality-assessment/index.ts +0 -97
- package/src/domains/quality-assessment/interfaces.ts +0 -152
- package/src/domains/quality-assessment/plugin.ts +0 -496
- package/src/domains/quality-assessment/services/coherence-gate.ts +0 -358
- package/src/domains/quality-assessment/services/deployment-advisor.ts +0 -571
- package/src/domains/quality-assessment/services/index.ts +0 -34
- package/src/domains/quality-assessment/services/quality-analyzer.ts +0 -670
- package/src/domains/quality-assessment/services/quality-gate.ts +0 -384
- package/src/domains/requirements-validation/coordinator.ts +0 -812
- package/src/domains/requirements-validation/index.ts +0 -92
- package/src/domains/requirements-validation/interfaces.ts +0 -303
- package/src/domains/requirements-validation/plugin.ts +0 -576
- package/src/domains/requirements-validation/services/bdd-scenario-writer.ts +0 -676
- package/src/domains/requirements-validation/services/index.ts +0 -20
- package/src/domains/requirements-validation/services/requirements-validator.ts +0 -559
- package/src/domains/requirements-validation/services/testability-scorer.ts +0 -639
- package/src/domains/security-compliance/coordinator.ts +0 -757
- package/src/domains/security-compliance/index.ts +0 -120
- package/src/domains/security-compliance/interfaces.ts +0 -434
- package/src/domains/security-compliance/plugin.ts +0 -509
- package/src/domains/security-compliance/services/compliance-validator.ts +0 -1226
- package/src/domains/security-compliance/services/index.ts +0 -31
- package/src/domains/security-compliance/services/security-auditor.ts +0 -2227
- package/src/domains/security-compliance/services/security-scanner.ts +0 -2354
- package/src/domains/security-compliance/services/semgrep-integration.ts +0 -289
- package/src/domains/test-execution/coordinator.ts +0 -426
- package/src/domains/test-execution/index.ts +0 -76
- package/src/domains/test-execution/interfaces.ts +0 -119
- package/src/domains/test-execution/plugin.ts +0 -208
- package/src/domains/test-execution/services/flaky-detector.ts +0 -1240
- package/src/domains/test-execution/services/index.ts +0 -8
- package/src/domains/test-execution/services/retry-handler.ts +0 -820
- package/src/domains/test-execution/services/test-executor.ts +0 -885
- package/src/domains/test-generation/coordinator.ts +0 -656
- package/src/domains/test-generation/index.ts +0 -77
- package/src/domains/test-generation/interfaces.ts +0 -118
- package/src/domains/test-generation/plugin.ts +0 -397
- package/src/domains/test-generation/services/index.ts +0 -23
- package/src/domains/test-generation/services/pattern-matcher.ts +0 -1725
- package/src/domains/test-generation/services/test-generator.ts +0 -2750
- package/src/domains/visual-accessibility/coordinator.ts +0 -860
- package/src/domains/visual-accessibility/index.ts +0 -116
- package/src/domains/visual-accessibility/interfaces.ts +0 -435
- package/src/domains/visual-accessibility/plugin.ts +0 -568
- package/src/domains/visual-accessibility/services/accessibility-tester.ts +0 -982
- package/src/domains/visual-accessibility/services/axe-core-audit.ts +0 -630
- package/src/domains/visual-accessibility/services/index.ts +0 -28
- package/src/domains/visual-accessibility/services/responsive-tester.ts +0 -934
- package/src/domains/visual-accessibility/services/visual-tester.ts +0 -458
- package/src/early-exit/early-exit-controller.ts +0 -490
- package/src/early-exit/early-exit-decision.ts +0 -391
- package/src/early-exit/index.ts +0 -115
- package/src/early-exit/quality-signal.ts +0 -389
- package/src/early-exit/speculative-executor.ts +0 -505
- package/src/early-exit/types.ts +0 -407
- package/src/feedback/coverage-learner.ts +0 -456
- package/src/feedback/feedback-loop.ts +0 -426
- package/src/feedback/index.ts +0 -72
- package/src/feedback/pattern-promotion.ts +0 -373
- package/src/feedback/quality-score-calculator.ts +0 -334
- package/src/feedback/test-outcome-tracker.ts +0 -450
- package/src/feedback/types.ts +0 -497
- package/src/index.ts +0 -224
- package/src/init/agents-installer.ts +0 -536
- package/src/init/index.ts +0 -80
- package/src/init/init-wizard.ts +0 -1061
- package/src/init/project-analyzer.ts +0 -696
- package/src/init/self-configurator.ts +0 -488
- package/src/init/skills-installer.ts +0 -467
- package/src/init/types.ts +0 -432
- package/src/integrations/ruvector/ast-complexity.ts +0 -470
- package/src/integrations/ruvector/coverage-router.ts +0 -594
- package/src/integrations/ruvector/diff-risk-classifier.ts +0 -759
- package/src/integrations/ruvector/fallback.ts +0 -942
- package/src/integrations/ruvector/graph-boundaries.ts +0 -809
- package/src/integrations/ruvector/index.ts +0 -363
- package/src/integrations/ruvector/interfaces.ts +0 -609
- package/src/integrations/ruvector/q-learning-router.ts +0 -550
- package/src/kernel/agent-coordinator.ts +0 -165
- package/src/kernel/agentdb-backend.ts +0 -504
- package/src/kernel/event-bus.ts +0 -129
- package/src/kernel/hybrid-backend.ts +0 -538
- package/src/kernel/index.ts +0 -28
- package/src/kernel/interfaces.ts +0 -257
- package/src/kernel/kernel.ts +0 -285
- package/src/kernel/memory-backend.ts +0 -169
- package/src/kernel/memory-factory.ts +0 -293
- package/src/kernel/plugin-loader.ts +0 -179
- package/src/learning/index.ts +0 -219
- package/src/learning/pattern-store.ts +0 -990
- package/src/learning/qe-guidance.ts +0 -832
- package/src/learning/qe-hooks.ts +0 -644
- package/src/learning/qe-patterns.ts +0 -449
- package/src/learning/qe-reasoning-bank.ts +0 -951
- package/src/learning/real-embeddings.ts +0 -277
- package/src/learning/real-qe-reasoning-bank.ts +0 -833
- package/src/learning/sqlite-persistence.ts +0 -554
- package/src/mcp/entry.ts +0 -59
- package/src/mcp/handlers/agent-handlers.ts +0 -285
- package/src/mcp/handlers/core-handlers.ts +0 -317
- package/src/mcp/handlers/domain-handlers.ts +0 -1444
- package/src/mcp/handlers/index.ts +0 -57
- package/src/mcp/handlers/memory-handlers.ts +0 -338
- package/src/mcp/handlers/task-handlers.ts +0 -363
- package/src/mcp/index.ts +0 -30
- package/src/mcp/metrics/index.ts +0 -14
- package/src/mcp/metrics/metrics-collector.ts +0 -503
- package/src/mcp/protocol-server.ts +0 -752
- package/src/mcp/security/cve-prevention.ts +0 -742
- package/src/mcp/security/index.ts +0 -356
- package/src/mcp/security/oauth21-provider.ts +0 -821
- package/src/mcp/security/rate-limiter.ts +0 -615
- package/src/mcp/security/sampling-server.ts +0 -662
- package/src/mcp/security/schema-validator.ts +0 -855
- package/src/mcp/server.ts +0 -657
- package/src/mcp/tool-registry.ts +0 -391
- package/src/mcp/tools/base.ts +0 -399
- package/src/mcp/tools/chaos-resilience/inject.ts +0 -699
- package/src/mcp/tools/code-intelligence/analyze.ts +0 -745
- package/src/mcp/tools/contract-testing/validate.ts +0 -708
- package/src/mcp/tools/coverage-analysis/index.ts +0 -770
- package/src/mcp/tools/defect-intelligence/predict.ts +0 -466
- package/src/mcp/tools/index.ts +0 -214
- package/src/mcp/tools/learning-optimization/optimize.ts +0 -772
- package/src/mcp/tools/quality-assessment/evaluate.ts +0 -385
- package/src/mcp/tools/registry.ts +0 -248
- package/src/mcp/tools/requirements-validation/validate.ts +0 -394
- package/src/mcp/tools/security-compliance/scan.ts +0 -365
- package/src/mcp/tools/test-execution/execute.ts +0 -291
- package/src/mcp/tools/test-generation/generate.ts +0 -544
- package/src/mcp/tools/visual-accessibility/index.ts +0 -791
- package/src/mcp/transport/index.ts +0 -31
- package/src/mcp/transport/stdio.ts +0 -318
- package/src/mcp/types.ts +0 -543
- package/src/neural-optimizer/index.ts +0 -111
- package/src/neural-optimizer/replay-buffer.ts +0 -455
- package/src/neural-optimizer/swarm-topology.ts +0 -508
- package/src/neural-optimizer/topology-optimizer.ts +0 -828
- package/src/neural-optimizer/types.ts +0 -481
- package/src/neural-optimizer/value-network.ts +0 -351
- package/src/optimization/auto-tuner.ts +0 -817
- package/src/optimization/index.ts +0 -77
- package/src/optimization/metric-collectors.ts +0 -474
- package/src/optimization/qe-workers.ts +0 -704
- package/src/optimization/tuning-algorithm.ts +0 -401
- package/src/optimization/types.ts +0 -314
- package/src/routing/index.ts +0 -51
- package/src/routing/qe-agent-registry.ts +0 -963
- package/src/routing/qe-task-router.ts +0 -564
- package/src/routing/routing-feedback.ts +0 -365
- package/src/routing/types.ts +0 -406
- package/src/shared/embeddings/embedding-cache.ts +0 -157
- package/src/shared/embeddings/index.ts +0 -50
- package/src/shared/embeddings/nomic-embedder.ts +0 -404
- package/src/shared/embeddings/ollama-client.ts +0 -195
- package/src/shared/embeddings/types.ts +0 -147
- package/src/shared/entities/agent.ts +0 -141
- package/src/shared/entities/base-entity.ts +0 -79
- package/src/shared/entities/index.ts +0 -6
- package/src/shared/events/domain-events.ts +0 -259
- package/src/shared/events/index.ts +0 -5
- package/src/shared/git/git-analyzer.ts +0 -656
- package/src/shared/git/index.ts +0 -11
- package/src/shared/http/http-client.ts +0 -420
- package/src/shared/http/index.ts +0 -13
- package/src/shared/index.ts +0 -41
- package/src/shared/io/file-reader.ts +0 -525
- package/src/shared/io/index.ts +0 -25
- package/src/shared/llm/cache.ts +0 -473
- package/src/shared/llm/circuit-breaker.ts +0 -369
- package/src/shared/llm/cost-tracker.ts +0 -460
- package/src/shared/llm/index.ts +0 -140
- package/src/shared/llm/interfaces.ts +0 -629
- package/src/shared/llm/provider-manager.ts +0 -685
- package/src/shared/llm/providers/claude.ts +0 -524
- package/src/shared/llm/providers/index.ts +0 -8
- package/src/shared/llm/providers/ollama.ts +0 -575
- package/src/shared/llm/providers/openai.ts +0 -609
- package/src/shared/metrics/code-metrics.ts +0 -520
- package/src/shared/metrics/index.ts +0 -23
- package/src/shared/metrics/system-metrics.ts +0 -353
- package/src/shared/parsers/index.ts +0 -6
- package/src/shared/parsers/typescript-parser.ts +0 -841
- package/src/shared/security/compliance-patterns.ts +0 -666
- package/src/shared/security/index.ts +0 -30
- package/src/shared/security/osv-client.ts +0 -468
- package/src/shared/types/index.ts +0 -150
- package/src/shared/value-objects/index.ts +0 -273
- package/src/strange-loop/healing-controller.ts +0 -833
- package/src/strange-loop/index.ts +0 -104
- package/src/strange-loop/self-model.ts +0 -494
- package/src/strange-loop/strange-loop.ts +0 -446
- package/src/strange-loop/swarm-observer.ts +0 -448
- package/src/strange-loop/topology-analyzer.ts +0 -565
- package/src/strange-loop/types.ts +0 -640
- package/src/time-crystal/default-phases.ts +0 -520
- package/src/time-crystal/index.ts +0 -164
- package/src/time-crystal/oscillator.ts +0 -425
- package/src/time-crystal/phase-executor.ts +0 -521
- package/src/time-crystal/scheduler.ts +0 -1025
- package/src/time-crystal/test-runner.ts +0 -787
- package/src/time-crystal/types.ts +0 -421
- package/src/workers/base-worker.ts +0 -304
- package/src/workers/daemon.ts +0 -264
- package/src/workers/index.ts +0 -119
- package/src/workers/interfaces.ts +0 -393
- package/src/workers/worker-manager.ts +0 -424
- package/src/workers/workers/compliance-checker.ts +0 -445
- package/src/workers/workers/coverage-tracker.ts +0 -344
- package/src/workers/workers/defect-predictor.ts +0 -375
- package/src/workers/workers/flaky-detector.ts +0 -390
- package/src/workers/workers/index.ts +0 -17
- package/src/workers/workers/learning-consolidation.ts +0 -442
- package/src/workers/workers/performance-baseline.ts +0 -434
- package/src/workers/workers/quality-gate.ts +0 -419
- package/src/workers/workers/regression-monitor.ts +0 -357
- package/src/workers/workers/security-scan.ts +0 -349
- package/src/workers/workers/test-health.ts +0 -359
- package/tests/integration/code-intelligence/knowledge-graph-real.test.ts +0 -540
- package/tests/integration/coordination/cross-domain-router.test.ts +0 -403
- package/tests/integration/coordination/protocol-executor.test.ts +0 -454
- package/tests/integration/coordination/workflow-orchestrator.test.ts +0 -418
- package/tests/integration/feedback/feedback-loop-integration.test.ts +0 -560
- package/tests/integration/migration/v2-to-v3-migration.test.ts +0 -471
- package/tests/integration/parsers/typescript-parser.test.ts +0 -463
- package/tests/integration/security/vulnerability-detection.test.ts +0 -628
- package/tests/integration/test-execution/coordinator.test.ts +0 -410
- package/tests/integration/test-generation/coordinator.test.ts +0 -361
- package/tests/mocks/index.ts +0 -228
- package/tests/time-crystal/default-phases.test.ts +0 -476
- package/tests/time-crystal/oscillator.test.ts +0 -541
- package/tests/time-crystal/phase-executor.test.ts +0 -653
- package/tests/time-crystal/scheduler.test.ts +0 -626
- package/tests/time-crystal/test-runner.test.ts +0 -594
- package/tests/unit/causal-discovery/causal-graph.test.ts +0 -504
- package/tests/unit/causal-discovery/causal-root-cause-analyzer.test.ts +0 -347
- package/tests/unit/causal-discovery/discovery-engine.test.ts +0 -435
- package/tests/unit/causal-discovery/weight-matrix.test.ts +0 -328
- package/tests/unit/cli/cli.test.ts +0 -341
- package/tests/unit/cli/commands.test.ts +0 -414
- package/tests/unit/cli/init-command.test.ts +0 -274
- package/tests/unit/cli/migrate-command.test.ts +0 -396
- package/tests/unit/coordination/claims/claim-service.test.ts +0 -949
- package/tests/unit/coordination/claims/handoff-manager.test.ts +0 -773
- package/tests/unit/coordination/claims/work-stealing.test.ts +0 -492
- package/tests/unit/coordination/queen-coordinator.test.ts +0 -966
- package/tests/unit/coordination/result-saver.test.ts +0 -653
- package/tests/unit/coordination/task-executor.test.ts +0 -810
- package/tests/unit/domains/chaos-resilience/chaos-engineer.test.ts +0 -484
- package/tests/unit/domains/chaos-resilience/load-tester.test.ts +0 -559
- package/tests/unit/domains/chaos-resilience/performance-profiler.test.ts +0 -490
- package/tests/unit/domains/code-intelligence/impact-analyzer.test.ts +0 -560
- package/tests/unit/domains/code-intelligence/knowledge-graph.test.ts +0 -460
- package/tests/unit/domains/code-intelligence/semantic-analyzer.test.ts +0 -584
- package/tests/unit/domains/contract-testing/api-compatibility.test.ts +0 -483
- package/tests/unit/domains/contract-testing/contract-validator.test.ts +0 -370
- package/tests/unit/domains/contract-testing/schema-validator.test.ts +0 -610
- package/tests/unit/domains/coverage-analysis/coverage-embedder.test.ts +0 -298
- package/tests/unit/domains/coverage-analysis/hnsw-index.test.ts +0 -292
- package/tests/unit/domains/coverage-analysis/sublinear-analyzer.test.ts +0 -506
- package/tests/unit/domains/defect-intelligence/defect-predictor.test.ts +0 -370
- package/tests/unit/domains/defect-intelligence/pattern-learner.test.ts +0 -546
- package/tests/unit/domains/defect-intelligence/root-cause-analyzer.test.ts +0 -534
- package/tests/unit/domains/learning-optimization/learning-coordinator.test.ts +0 -541
- package/tests/unit/domains/learning-optimization/metrics-optimizer.test.ts +0 -552
- package/tests/unit/domains/learning-optimization/production-intel.test.ts +0 -589
- package/tests/unit/domains/learning-optimization/transfer-specialist.test.ts +0 -453
- package/tests/unit/domains/quality-assessment/coherence-gate.test.ts +0 -1006
- package/tests/unit/domains/quality-assessment/deployment-advisor.test.ts +0 -515
- package/tests/unit/domains/quality-assessment/quality-analyzer.test.ts +0 -401
- package/tests/unit/domains/quality-assessment/quality-gate.test.ts +0 -324
- package/tests/unit/domains/requirements-validation/bdd-scenario-writer.test.ts +0 -479
- package/tests/unit/domains/requirements-validation/requirements-validator.test.ts +0 -452
- package/tests/unit/domains/requirements-validation/testability-scorer.test.ts +0 -505
- package/tests/unit/domains/security-compliance/compliance-validator.test.ts +0 -500
- package/tests/unit/domains/security-compliance/security-auditor.test.ts +0 -498
- package/tests/unit/domains/security-compliance/security-scanner.test.ts +0 -412
- package/tests/unit/domains/visual-accessibility/accessibility-tester.test.ts +0 -432
- package/tests/unit/domains/visual-accessibility/responsive-tester.test.ts +0 -506
- package/tests/unit/domains/visual-accessibility/visual-tester.test.ts +0 -412
- package/tests/unit/early-exit/early-exit-controller.test.ts +0 -548
- package/tests/unit/early-exit/early-exit-decision.test.ts +0 -617
- package/tests/unit/early-exit/index.test.ts +0 -254
- package/tests/unit/early-exit/quality-signal.test.ts +0 -589
- package/tests/unit/early-exit/speculative-executor.test.ts +0 -453
- package/tests/unit/feedback/coverage-learner.test.ts +0 -288
- package/tests/unit/feedback/feedback-loop.test.ts +0 -458
- package/tests/unit/feedback/pattern-promotion.test.ts +0 -390
- package/tests/unit/feedback/quality-score-calculator.test.ts +0 -364
- package/tests/unit/feedback/test-outcome-tracker.test.ts +0 -243
- package/tests/unit/init/init-wizard.test.ts +0 -881
- package/tests/unit/init/project-analyzer.test.ts +0 -807
- package/tests/unit/init/self-configurator.test.ts +0 -493
- package/tests/unit/integrations/ruvector/ast-complexity.test.ts +0 -240
- package/tests/unit/integrations/ruvector/coverage-router.test.ts +0 -366
- package/tests/unit/integrations/ruvector/diff-risk-classifier.test.ts +0 -340
- package/tests/unit/integrations/ruvector/graph-boundaries.test.ts +0 -355
- package/tests/unit/integrations/ruvector/q-learning-router.test.ts +0 -314
- package/tests/unit/kernel/agent-coordinator.test.ts +0 -220
- package/tests/unit/kernel/event-bus.test.ts +0 -197
- package/tests/unit/learning/qe-reasoning-bank.test.ts +0 -666
- package/tests/unit/learning/real-qe-reasoning-bank.benchmark.test.ts +0 -415
- package/tests/unit/mcp/mcp-server.test.ts +0 -544
- package/tests/unit/mcp/metrics/metrics-collector.test.ts +0 -340
- package/tests/unit/mcp/security/cve-prevention.test.ts +0 -512
- package/tests/unit/mcp/security/oauth21-provider.test.ts +0 -624
- package/tests/unit/mcp/security/rate-limiter.test.ts +0 -410
- package/tests/unit/mcp/security/sampling-server.test.ts +0 -420
- package/tests/unit/mcp/security/schema-validator.test.ts +0 -494
- package/tests/unit/mcp/tools/base.test.ts +0 -336
- package/tests/unit/mcp/tools/domain-tools.test.ts +0 -759
- package/tests/unit/mcp/tools/registry.test.ts +0 -240
- package/tests/unit/neural-optimizer/replay-buffer.test.ts +0 -403
- package/tests/unit/neural-optimizer/swarm-topology.test.ts +0 -473
- package/tests/unit/neural-optimizer/topology-optimizer.test.ts +0 -595
- package/tests/unit/neural-optimizer/value-network.test.ts +0 -343
- package/tests/unit/optimization/auto-tuner.test.ts +0 -506
- package/tests/unit/optimization/metric-collectors.test.ts +0 -352
- package/tests/unit/optimization/qe-workers.test.ts +0 -407
- package/tests/unit/optimization/tuning-algorithm.test.ts +0 -467
- package/tests/unit/routing/qe-agent-registry.test.ts +0 -229
- package/tests/unit/routing/qe-task-router.test.ts +0 -390
- package/tests/unit/routing/routing-feedback.test.ts +0 -339
- package/tests/unit/shared/embeddings/nomic-embedder.test.ts +0 -419
- package/tests/unit/shared/http/http-client.test.ts +0 -719
- package/tests/unit/shared/io/file-reader.test.ts +0 -511
- package/tests/unit/shared/llm/cache.test.ts +0 -391
- package/tests/unit/shared/llm/circuit-breaker.test.ts +0 -293
- package/tests/unit/shared/llm/cost-tracker.test.ts +0 -431
- package/tests/unit/shared/llm/provider-manager.test.ts +0 -550
- package/tests/unit/shared/llm/providers.test.ts +0 -532
- package/tests/unit/shared/parsers/typescript-parser.test.ts +0 -693
- package/tests/unit/shared/value-objects.test.ts +0 -184
- package/tests/unit/strange-loop/strange-loop.test.ts +0 -1170
- package/tests/unit/workers/base-worker.test.ts +0 -341
- package/tests/unit/workers/daemon.test.ts +0 -291
- package/tests/unit/workers/worker-manager.test.ts +0 -284
- package/tsconfig.json +0 -32
- package/vitest.config.ts +0 -27
|
@@ -1,1700 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agentic QE v3 - Contract Validator Service
|
|
3
|
-
* Implements IContractValidationService for API contract validation (Pact-style)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Result, ok, err } from '../../../shared/types/index.js';
|
|
7
|
-
import type { MemoryBackend } from '../../../kernel/interfaces.js';
|
|
8
|
-
import type {
|
|
9
|
-
IContractValidationService,
|
|
10
|
-
ApiContract,
|
|
11
|
-
SchemaDefinition,
|
|
12
|
-
ValidationReport,
|
|
13
|
-
ValidationError,
|
|
14
|
-
SchemaValidationResult,
|
|
15
|
-
SchemaError,
|
|
16
|
-
OpenAPIValidationResult,
|
|
17
|
-
ContractEndpoint,
|
|
18
|
-
} from '../interfaces.js';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Configuration for the contract validator
|
|
22
|
-
*/
|
|
23
|
-
export interface ContractValidatorConfig {
|
|
24
|
-
strictMode: boolean;
|
|
25
|
-
validateExamples: boolean;
|
|
26
|
-
maxSchemaDepth: number;
|
|
27
|
-
cacheValidations: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const DEFAULT_CONFIG: ContractValidatorConfig = {
|
|
31
|
-
strictMode: true,
|
|
32
|
-
validateExamples: true,
|
|
33
|
-
maxSchemaDepth: 20,
|
|
34
|
-
cacheValidations: true,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* GraphQL schema parsing types
|
|
39
|
-
*/
|
|
40
|
-
interface GraphQLSchemaInfo {
|
|
41
|
-
types: Record<string, GraphQLTypeDef>;
|
|
42
|
-
queryType: string | null;
|
|
43
|
-
mutationType: string | null;
|
|
44
|
-
subscriptionType: string | null;
|
|
45
|
-
errors: string[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface GraphQLTypeDef {
|
|
49
|
-
kind: 'type' | 'input' | 'interface' | 'enum';
|
|
50
|
-
fields: Record<string, GraphQLFieldDef>;
|
|
51
|
-
implements: string | null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface GraphQLFieldDef {
|
|
55
|
-
type: string;
|
|
56
|
-
arguments?: Record<string, { type: string }>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface GraphQLOperationInfo {
|
|
60
|
-
type: 'query' | 'mutation' | 'subscription';
|
|
61
|
-
name: string | null;
|
|
62
|
-
fields: GraphQLField[];
|
|
63
|
-
variableDefinitions: Record<string, { type: string; required: boolean }>;
|
|
64
|
-
errors: string[];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface GraphQLField {
|
|
68
|
-
name: string;
|
|
69
|
-
alias?: string;
|
|
70
|
-
arguments?: Record<string, unknown>;
|
|
71
|
-
selections?: GraphQLField[];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Contract Validation Service Implementation
|
|
76
|
-
* Validates API contracts against schemas and specifications
|
|
77
|
-
*/
|
|
78
|
-
export class ContractValidatorService implements IContractValidationService {
|
|
79
|
-
private readonly config: ContractValidatorConfig;
|
|
80
|
-
private readonly validationCache: Map<string, ValidationReport> = new Map();
|
|
81
|
-
|
|
82
|
-
constructor(
|
|
83
|
-
private readonly memory: MemoryBackend,
|
|
84
|
-
config: Partial<ContractValidatorConfig> = {}
|
|
85
|
-
) {
|
|
86
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Validate contract structure
|
|
91
|
-
*/
|
|
92
|
-
async validateContract(contract: ApiContract): Promise<Result<ValidationReport>> {
|
|
93
|
-
try {
|
|
94
|
-
// Check cache first
|
|
95
|
-
const cacheKey = this.getCacheKey(contract);
|
|
96
|
-
if (this.config.cacheValidations && this.validationCache.has(cacheKey)) {
|
|
97
|
-
return ok(this.validationCache.get(cacheKey)!);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const errors: ValidationError[] = [];
|
|
101
|
-
const warnings: string[] = [];
|
|
102
|
-
|
|
103
|
-
// Validate basic contract structure
|
|
104
|
-
this.validateContractStructure(contract, errors, warnings);
|
|
105
|
-
|
|
106
|
-
// Validate provider info
|
|
107
|
-
this.validateServiceInfo(contract.provider, 'provider', errors);
|
|
108
|
-
|
|
109
|
-
// Validate consumers
|
|
110
|
-
for (const consumer of contract.consumers) {
|
|
111
|
-
this.validateServiceInfo(consumer, `consumer:${consumer.name}`, errors);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Validate endpoints
|
|
115
|
-
for (const endpoint of contract.endpoints) {
|
|
116
|
-
await this.validateEndpoint(endpoint, contract.schemas, errors, warnings);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Validate schemas
|
|
120
|
-
for (const schema of contract.schemas) {
|
|
121
|
-
await this.validateSchemaDefinition(schema, errors, warnings);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Validate examples if enabled
|
|
125
|
-
if (this.config.validateExamples) {
|
|
126
|
-
await this.validateEndpointExamples(contract, errors, warnings);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const report: ValidationReport = {
|
|
130
|
-
isValid: errors.length === 0,
|
|
131
|
-
errors,
|
|
132
|
-
warnings,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// Cache the result
|
|
136
|
-
if (this.config.cacheValidations) {
|
|
137
|
-
this.validationCache.set(cacheKey, report);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Store validation history
|
|
141
|
-
await this.storeValidationHistory(contract.id, report);
|
|
142
|
-
|
|
143
|
-
return ok(report);
|
|
144
|
-
} catch (error) {
|
|
145
|
-
return err(error instanceof Error ? error : new Error(String(error)));
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Validate request against schema
|
|
151
|
-
*/
|
|
152
|
-
async validateRequest(
|
|
153
|
-
request: unknown,
|
|
154
|
-
schema: SchemaDefinition
|
|
155
|
-
): Promise<Result<SchemaValidationResult>> {
|
|
156
|
-
try {
|
|
157
|
-
const errors: SchemaError[] = [];
|
|
158
|
-
|
|
159
|
-
switch (schema.type) {
|
|
160
|
-
case 'json-schema':
|
|
161
|
-
await this.validateAgainstJsonSchema(request, schema.content, errors);
|
|
162
|
-
break;
|
|
163
|
-
case 'openapi':
|
|
164
|
-
await this.validateAgainstOpenAPISchema(request, schema.content, errors);
|
|
165
|
-
break;
|
|
166
|
-
case 'graphql':
|
|
167
|
-
await this.validateAgainstGraphQLSchema(request, schema.content, errors);
|
|
168
|
-
break;
|
|
169
|
-
default:
|
|
170
|
-
errors.push({
|
|
171
|
-
path: '',
|
|
172
|
-
keyword: 'unsupported',
|
|
173
|
-
message: `Schema type '${schema.type}' is not supported for request validation`,
|
|
174
|
-
params: { schemaType: schema.type },
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return ok({
|
|
179
|
-
isValid: errors.length === 0,
|
|
180
|
-
errors,
|
|
181
|
-
});
|
|
182
|
-
} catch (error) {
|
|
183
|
-
return err(error instanceof Error ? error : new Error(String(error)));
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Validate response against schema
|
|
189
|
-
*/
|
|
190
|
-
async validateResponse(
|
|
191
|
-
response: unknown,
|
|
192
|
-
schema: SchemaDefinition
|
|
193
|
-
): Promise<Result<SchemaValidationResult>> {
|
|
194
|
-
try {
|
|
195
|
-
const errors: SchemaError[] = [];
|
|
196
|
-
|
|
197
|
-
switch (schema.type) {
|
|
198
|
-
case 'json-schema':
|
|
199
|
-
await this.validateAgainstJsonSchema(response, schema.content, errors);
|
|
200
|
-
break;
|
|
201
|
-
case 'openapi':
|
|
202
|
-
await this.validateAgainstOpenAPISchema(response, schema.content, errors);
|
|
203
|
-
break;
|
|
204
|
-
case 'graphql':
|
|
205
|
-
await this.validateAgainstGraphQLSchema(response, schema.content, errors);
|
|
206
|
-
break;
|
|
207
|
-
default:
|
|
208
|
-
errors.push({
|
|
209
|
-
path: '',
|
|
210
|
-
keyword: 'unsupported',
|
|
211
|
-
message: `Schema type '${schema.type}' is not supported for response validation`,
|
|
212
|
-
params: { schemaType: schema.type },
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return ok({
|
|
217
|
-
isValid: errors.length === 0,
|
|
218
|
-
errors,
|
|
219
|
-
});
|
|
220
|
-
} catch (error) {
|
|
221
|
-
return err(error instanceof Error ? error : new Error(String(error)));
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Validate OpenAPI/Swagger specification
|
|
227
|
-
*/
|
|
228
|
-
async validateOpenAPI(spec: string): Promise<Result<OpenAPIValidationResult>> {
|
|
229
|
-
try {
|
|
230
|
-
const errors: ValidationError[] = [];
|
|
231
|
-
const warnings: string[] = [];
|
|
232
|
-
let specVersion = 'unknown';
|
|
233
|
-
let endpointCount = 0;
|
|
234
|
-
let schemaCount = 0;
|
|
235
|
-
|
|
236
|
-
// Parse the spec
|
|
237
|
-
let parsedSpec: Record<string, unknown>;
|
|
238
|
-
try {
|
|
239
|
-
parsedSpec = JSON.parse(spec);
|
|
240
|
-
} catch {
|
|
241
|
-
// Try YAML parsing
|
|
242
|
-
// For now, we'll treat it as JSON
|
|
243
|
-
return ok({
|
|
244
|
-
isValid: false,
|
|
245
|
-
specVersion: 'unknown',
|
|
246
|
-
errors: [
|
|
247
|
-
{
|
|
248
|
-
path: '',
|
|
249
|
-
message: 'Failed to parse OpenAPI specification',
|
|
250
|
-
code: 'PARSE_ERROR',
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
warnings: [],
|
|
254
|
-
endpointCount: 0,
|
|
255
|
-
schemaCount: 0,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Detect spec version
|
|
260
|
-
if (parsedSpec.openapi) {
|
|
261
|
-
specVersion = parsedSpec.openapi as string;
|
|
262
|
-
} else if (parsedSpec.swagger) {
|
|
263
|
-
specVersion = `swagger-${parsedSpec.swagger}`;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Validate OpenAPI 3.x structure
|
|
267
|
-
if (specVersion.startsWith('3.')) {
|
|
268
|
-
this.validateOpenAPI3Structure(parsedSpec, errors, warnings);
|
|
269
|
-
} else if (specVersion.startsWith('swagger-2')) {
|
|
270
|
-
this.validateSwagger2Structure(parsedSpec, errors, warnings);
|
|
271
|
-
} else {
|
|
272
|
-
errors.push({
|
|
273
|
-
path: '',
|
|
274
|
-
message: `Unsupported OpenAPI version: ${specVersion}`,
|
|
275
|
-
code: 'UNSUPPORTED_VERSION',
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Count endpoints
|
|
280
|
-
const paths = (parsedSpec.paths as Record<string, unknown>) || {};
|
|
281
|
-
for (const [_path, methods] of Object.entries(paths)) {
|
|
282
|
-
if (typeof methods === 'object' && methods !== null) {
|
|
283
|
-
const httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];
|
|
284
|
-
for (const method of httpMethods) {
|
|
285
|
-
if (method in methods) {
|
|
286
|
-
endpointCount++;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Count schemas
|
|
293
|
-
const components = parsedSpec.components as Record<string, unknown> | undefined;
|
|
294
|
-
const schemas = (components?.schemas as Record<string, unknown>) || {};
|
|
295
|
-
schemaCount = Object.keys(schemas).length;
|
|
296
|
-
|
|
297
|
-
// Also count definitions for Swagger 2
|
|
298
|
-
const definitions = (parsedSpec.definitions as Record<string, unknown>) || {};
|
|
299
|
-
schemaCount += Object.keys(definitions).length;
|
|
300
|
-
|
|
301
|
-
return ok({
|
|
302
|
-
isValid: errors.length === 0,
|
|
303
|
-
specVersion,
|
|
304
|
-
errors,
|
|
305
|
-
warnings,
|
|
306
|
-
endpointCount,
|
|
307
|
-
schemaCount,
|
|
308
|
-
});
|
|
309
|
-
} catch (error) {
|
|
310
|
-
return err(error instanceof Error ? error : new Error(String(error)));
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// ============================================================================
|
|
315
|
-
// Private Helper Methods
|
|
316
|
-
// ============================================================================
|
|
317
|
-
|
|
318
|
-
private getCacheKey(contract: ApiContract): string {
|
|
319
|
-
return `${contract.id}:${contract.version.toString()}`;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
private validateContractStructure(
|
|
323
|
-
contract: ApiContract,
|
|
324
|
-
errors: ValidationError[],
|
|
325
|
-
warnings: string[]
|
|
326
|
-
): void {
|
|
327
|
-
if (!contract.id || contract.id.trim() === '') {
|
|
328
|
-
errors.push({
|
|
329
|
-
path: 'id',
|
|
330
|
-
message: 'Contract ID is required',
|
|
331
|
-
code: 'REQUIRED_FIELD',
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (!contract.name || contract.name.trim() === '') {
|
|
336
|
-
errors.push({
|
|
337
|
-
path: 'name',
|
|
338
|
-
message: 'Contract name is required',
|
|
339
|
-
code: 'REQUIRED_FIELD',
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (!contract.type) {
|
|
344
|
-
errors.push({
|
|
345
|
-
path: 'type',
|
|
346
|
-
message: 'Contract type is required',
|
|
347
|
-
code: 'REQUIRED_FIELD',
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (contract.endpoints.length === 0) {
|
|
352
|
-
if (this.config.strictMode) {
|
|
353
|
-
errors.push({
|
|
354
|
-
path: 'endpoints',
|
|
355
|
-
message: 'Contract must have at least one endpoint',
|
|
356
|
-
code: 'EMPTY_ENDPOINTS',
|
|
357
|
-
});
|
|
358
|
-
} else {
|
|
359
|
-
warnings.push('Contract has no endpoints defined');
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (contract.consumers.length === 0) {
|
|
364
|
-
warnings.push('No consumers defined for contract');
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
private validateServiceInfo(
|
|
369
|
-
info: { name: string; version: string; team?: string },
|
|
370
|
-
path: string,
|
|
371
|
-
errors: ValidationError[]
|
|
372
|
-
): void {
|
|
373
|
-
if (!info.name || info.name.trim() === '') {
|
|
374
|
-
errors.push({
|
|
375
|
-
path: `${path}.name`,
|
|
376
|
-
message: 'Service name is required',
|
|
377
|
-
code: 'REQUIRED_FIELD',
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (!info.version || info.version.trim() === '') {
|
|
382
|
-
errors.push({
|
|
383
|
-
path: `${path}.version`,
|
|
384
|
-
message: 'Service version is required',
|
|
385
|
-
code: 'REQUIRED_FIELD',
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
private async validateEndpoint(
|
|
391
|
-
endpoint: ContractEndpoint,
|
|
392
|
-
schemas: SchemaDefinition[],
|
|
393
|
-
errors: ValidationError[],
|
|
394
|
-
warnings: string[]
|
|
395
|
-
): Promise<void> {
|
|
396
|
-
if (!endpoint.path || endpoint.path.trim() === '') {
|
|
397
|
-
errors.push({
|
|
398
|
-
path: 'endpoint.path',
|
|
399
|
-
message: 'Endpoint path is required',
|
|
400
|
-
code: 'REQUIRED_FIELD',
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (!endpoint.method) {
|
|
405
|
-
errors.push({
|
|
406
|
-
path: 'endpoint.method',
|
|
407
|
-
message: 'HTTP method is required',
|
|
408
|
-
code: 'REQUIRED_FIELD',
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Validate schema references
|
|
413
|
-
if (endpoint.requestSchema) {
|
|
414
|
-
const found = schemas.some((s) => s.id === endpoint.requestSchema);
|
|
415
|
-
if (!found) {
|
|
416
|
-
errors.push({
|
|
417
|
-
path: `endpoint.${endpoint.path}.requestSchema`,
|
|
418
|
-
message: `Request schema '${endpoint.requestSchema}' not found`,
|
|
419
|
-
code: 'INVALID_REFERENCE',
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (endpoint.responseSchema) {
|
|
425
|
-
const found = schemas.some((s) => s.id === endpoint.responseSchema);
|
|
426
|
-
if (!found) {
|
|
427
|
-
errors.push({
|
|
428
|
-
path: `endpoint.${endpoint.path}.responseSchema`,
|
|
429
|
-
message: `Response schema '${endpoint.responseSchema}' not found`,
|
|
430
|
-
code: 'INVALID_REFERENCE',
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Warn about missing examples
|
|
436
|
-
if (endpoint.examples.length === 0) {
|
|
437
|
-
warnings.push(`Endpoint ${endpoint.method} ${endpoint.path} has no examples`);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
private async validateSchemaDefinition(
|
|
442
|
-
schema: SchemaDefinition,
|
|
443
|
-
errors: ValidationError[],
|
|
444
|
-
warnings: string[]
|
|
445
|
-
): Promise<void> {
|
|
446
|
-
if (!schema.id || schema.id.trim() === '') {
|
|
447
|
-
errors.push({
|
|
448
|
-
path: 'schema.id',
|
|
449
|
-
message: 'Schema ID is required',
|
|
450
|
-
code: 'REQUIRED_FIELD',
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (!schema.content || schema.content.trim() === '') {
|
|
455
|
-
errors.push({
|
|
456
|
-
path: `schema.${schema.id}.content`,
|
|
457
|
-
message: 'Schema content is required',
|
|
458
|
-
code: 'REQUIRED_FIELD',
|
|
459
|
-
});
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Validate schema based on type
|
|
464
|
-
switch (schema.type) {
|
|
465
|
-
case 'json-schema':
|
|
466
|
-
this.validateJsonSchemaContent(schema.content, schema.id, errors, warnings);
|
|
467
|
-
break;
|
|
468
|
-
case 'openapi':
|
|
469
|
-
// OpenAPI schemas are validated as JSON Schema subset
|
|
470
|
-
this.validateJsonSchemaContent(schema.content, schema.id, errors, warnings);
|
|
471
|
-
break;
|
|
472
|
-
case 'graphql':
|
|
473
|
-
this.validateGraphQLSchemaContent(schema.content, schema.id, errors, warnings);
|
|
474
|
-
break;
|
|
475
|
-
case 'protobuf':
|
|
476
|
-
this.validateProtobufContent(schema.content, schema.id, errors, warnings);
|
|
477
|
-
break;
|
|
478
|
-
case 'avro':
|
|
479
|
-
this.validateAvroContent(schema.content, schema.id, errors, warnings);
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
private validateJsonSchemaContent(
|
|
485
|
-
content: string,
|
|
486
|
-
schemaId: string,
|
|
487
|
-
errors: ValidationError[],
|
|
488
|
-
_warnings: string[]
|
|
489
|
-
): void {
|
|
490
|
-
try {
|
|
491
|
-
const parsed = JSON.parse(content);
|
|
492
|
-
|
|
493
|
-
// Basic JSON Schema validation
|
|
494
|
-
if (typeof parsed !== 'object' || parsed === null) {
|
|
495
|
-
errors.push({
|
|
496
|
-
path: `schema.${schemaId}`,
|
|
497
|
-
message: 'JSON Schema must be an object',
|
|
498
|
-
code: 'INVALID_SCHEMA',
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
} catch {
|
|
502
|
-
errors.push({
|
|
503
|
-
path: `schema.${schemaId}`,
|
|
504
|
-
message: 'Invalid JSON in schema content',
|
|
505
|
-
code: 'INVALID_JSON',
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
private validateGraphQLSchemaContent(
|
|
511
|
-
content: string,
|
|
512
|
-
schemaId: string,
|
|
513
|
-
errors: ValidationError[],
|
|
514
|
-
warnings: string[]
|
|
515
|
-
): void {
|
|
516
|
-
// GraphQL schema validation with syntax checking
|
|
517
|
-
const lines = content.split('\n');
|
|
518
|
-
let braceDepth = 0;
|
|
519
|
-
let hasTypeDefinition = false;
|
|
520
|
-
let hasQueryType = false;
|
|
521
|
-
|
|
522
|
-
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
523
|
-
const line = lines[lineNum].trim();
|
|
524
|
-
|
|
525
|
-
// Skip comments and empty lines
|
|
526
|
-
if (line.startsWith('#') || line === '') continue;
|
|
527
|
-
|
|
528
|
-
// Check for type definitions
|
|
529
|
-
if (line.startsWith('type ') || line.startsWith('input ') || line.startsWith('interface ') || line.startsWith('enum ')) {
|
|
530
|
-
hasTypeDefinition = true;
|
|
531
|
-
if (line.startsWith('type Query')) {
|
|
532
|
-
hasQueryType = true;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Check for schema definition
|
|
537
|
-
if (line.startsWith('schema ') || line.startsWith('schema{')) {
|
|
538
|
-
hasTypeDefinition = true;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Track brace depth for balance check
|
|
542
|
-
braceDepth += (line.match(/{/g) || []).length;
|
|
543
|
-
braceDepth -= (line.match(/}/g) || []).length;
|
|
544
|
-
|
|
545
|
-
// Check for invalid syntax patterns
|
|
546
|
-
if (line.includes('::')) {
|
|
547
|
-
errors.push({
|
|
548
|
-
path: `schema.${schemaId}.line${lineNum + 1}`,
|
|
549
|
-
message: 'Invalid double colon in GraphQL schema',
|
|
550
|
-
code: 'INVALID_GRAPHQL_SYNTAX',
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Check for field definitions without type
|
|
555
|
-
if (line.includes(':') && !line.includes('type ') && !line.includes('schema')) {
|
|
556
|
-
const colonIndex = line.indexOf(':');
|
|
557
|
-
const afterColon = line.slice(colonIndex + 1).trim();
|
|
558
|
-
if (afterColon === '' || afterColon === '{') {
|
|
559
|
-
errors.push({
|
|
560
|
-
path: `schema.${schemaId}.line${lineNum + 1}`,
|
|
561
|
-
message: 'Field definition missing type',
|
|
562
|
-
code: 'INVALID_GRAPHQL_FIELD',
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Check for required elements
|
|
569
|
-
if (!hasTypeDefinition) {
|
|
570
|
-
errors.push({
|
|
571
|
-
path: `schema.${schemaId}`,
|
|
572
|
-
message: 'GraphQL schema must contain type definitions',
|
|
573
|
-
code: 'INVALID_GRAPHQL',
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Check for balanced braces
|
|
578
|
-
if (braceDepth !== 0) {
|
|
579
|
-
errors.push({
|
|
580
|
-
path: `schema.${schemaId}`,
|
|
581
|
-
message: 'GraphQL schema has unbalanced braces',
|
|
582
|
-
code: 'INVALID_GRAPHQL_BRACES',
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Warn if no Query type
|
|
587
|
-
if (hasTypeDefinition && !hasQueryType && !content.includes('schema {')) {
|
|
588
|
-
warnings.push(`Schema ${schemaId} has no Query type defined`);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
private validateProtobufContent(
|
|
593
|
-
content: string,
|
|
594
|
-
schemaId: string,
|
|
595
|
-
errors: ValidationError[],
|
|
596
|
-
warnings: string[]
|
|
597
|
-
): void {
|
|
598
|
-
// Protobuf schema validation with syntax checking
|
|
599
|
-
const lines = content.split('\n');
|
|
600
|
-
let braceDepth = 0;
|
|
601
|
-
let hasMessage = false;
|
|
602
|
-
let hasSyntax = false;
|
|
603
|
-
let syntaxVersion: string | null = null;
|
|
604
|
-
|
|
605
|
-
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
606
|
-
const line = lines[lineNum].trim();
|
|
607
|
-
|
|
608
|
-
// Skip comments and empty lines
|
|
609
|
-
if (line.startsWith('//') || line === '') continue;
|
|
610
|
-
|
|
611
|
-
// Check for syntax declaration
|
|
612
|
-
const syntaxMatch = line.match(/^syntax\s*=\s*["']proto(\d)["']\s*;?/);
|
|
613
|
-
if (syntaxMatch) {
|
|
614
|
-
hasSyntax = true;
|
|
615
|
-
syntaxVersion = syntaxMatch[1];
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// Check for message definitions
|
|
619
|
-
if (line.startsWith('message ')) {
|
|
620
|
-
hasMessage = true;
|
|
621
|
-
// Check for valid message name
|
|
622
|
-
const msgMatch = line.match(/^message\s+([A-Z][a-zA-Z0-9_]*)\s*\{?/);
|
|
623
|
-
if (!msgMatch) {
|
|
624
|
-
warnings.push(`Message name should start with uppercase letter at line ${lineNum + 1}`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Check for enum definitions
|
|
629
|
-
if (line.startsWith('enum ')) {
|
|
630
|
-
hasMessage = true; // Enums are also valid definitions
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Check for service definitions
|
|
634
|
-
if (line.startsWith('service ')) {
|
|
635
|
-
hasMessage = true;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// Track brace depth
|
|
639
|
-
braceDepth += (line.match(/{/g) || []).length;
|
|
640
|
-
braceDepth -= (line.match(/}/g) || []).length;
|
|
641
|
-
|
|
642
|
-
// Check for field definitions (inside messages)
|
|
643
|
-
const fieldMatch = line.match(/^\s*(optional|required|repeated)?\s*(\w+)\s+(\w+)\s*=\s*(\d+)/);
|
|
644
|
-
if (fieldMatch) {
|
|
645
|
-
const [, modifier, , , fieldNum] = fieldMatch;
|
|
646
|
-
const num = parseInt(fieldNum, 10);
|
|
647
|
-
|
|
648
|
-
// Field numbers should be positive and within valid range
|
|
649
|
-
if (num <= 0 || num > 536870911) {
|
|
650
|
-
errors.push({
|
|
651
|
-
path: `schema.${schemaId}.line${lineNum + 1}`,
|
|
652
|
-
message: `Invalid field number ${num}`,
|
|
653
|
-
code: 'INVALID_PROTOBUF_FIELD_NUMBER',
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// Warn about reserved field numbers
|
|
658
|
-
if (num >= 19000 && num <= 19999) {
|
|
659
|
-
warnings.push(`Field number ${num} is in reserved range (19000-19999) at line ${lineNum + 1}`);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// Check for required in proto3
|
|
663
|
-
if (modifier === 'required' && syntaxVersion === '3') {
|
|
664
|
-
errors.push({
|
|
665
|
-
path: `schema.${schemaId}.line${lineNum + 1}`,
|
|
666
|
-
message: 'Required fields are not allowed in proto3',
|
|
667
|
-
code: 'INVALID_PROTOBUF_REQUIRED',
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Check for required elements
|
|
674
|
-
if (!hasMessage) {
|
|
675
|
-
errors.push({
|
|
676
|
-
path: `schema.${schemaId}`,
|
|
677
|
-
message: 'Protobuf schema must contain message, enum, or service definitions',
|
|
678
|
-
code: 'INVALID_PROTOBUF',
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Check for balanced braces
|
|
683
|
-
if (braceDepth !== 0) {
|
|
684
|
-
errors.push({
|
|
685
|
-
path: `schema.${schemaId}`,
|
|
686
|
-
message: 'Protobuf schema has unbalanced braces',
|
|
687
|
-
code: 'INVALID_PROTOBUF_BRACES',
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Warn if no syntax declaration
|
|
692
|
-
if (!hasSyntax) {
|
|
693
|
-
warnings.push(`Schema ${schemaId} has no syntax declaration (defaults to proto2)`);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
private validateAvroContent(
|
|
698
|
-
content: string,
|
|
699
|
-
schemaId: string,
|
|
700
|
-
errors: ValidationError[],
|
|
701
|
-
_warnings: string[]
|
|
702
|
-
): void {
|
|
703
|
-
try {
|
|
704
|
-
const parsed = JSON.parse(content);
|
|
705
|
-
if (!parsed.type) {
|
|
706
|
-
errors.push({
|
|
707
|
-
path: `schema.${schemaId}`,
|
|
708
|
-
message: 'Avro schema must have a type field',
|
|
709
|
-
code: 'INVALID_AVRO',
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
} catch {
|
|
713
|
-
errors.push({
|
|
714
|
-
path: `schema.${schemaId}`,
|
|
715
|
-
message: 'Invalid JSON in Avro schema',
|
|
716
|
-
code: 'INVALID_JSON',
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
private async validateEndpointExamples(
|
|
722
|
-
contract: ApiContract,
|
|
723
|
-
errors: ValidationError[],
|
|
724
|
-
_warnings: string[]
|
|
725
|
-
): Promise<void> {
|
|
726
|
-
for (const endpoint of contract.endpoints) {
|
|
727
|
-
for (const example of endpoint.examples) {
|
|
728
|
-
// Validate request example against schema
|
|
729
|
-
if (endpoint.requestSchema && example.request !== undefined) {
|
|
730
|
-
const schema = contract.schemas.find((s) => s.id === endpoint.requestSchema);
|
|
731
|
-
if (schema) {
|
|
732
|
-
const result = await this.validateRequest(example.request, schema);
|
|
733
|
-
if (result.success && !result.value.isValid) {
|
|
734
|
-
errors.push({
|
|
735
|
-
path: `endpoint.${endpoint.path}.example.${example.name}.request`,
|
|
736
|
-
message: `Request example does not match schema: ${result.value.errors.map((e) => e.message).join(', ')}`,
|
|
737
|
-
code: 'EXAMPLE_VALIDATION_FAILED',
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// Validate response example against schema
|
|
744
|
-
if (endpoint.responseSchema && example.response !== undefined) {
|
|
745
|
-
const schema = contract.schemas.find((s) => s.id === endpoint.responseSchema);
|
|
746
|
-
if (schema) {
|
|
747
|
-
const result = await this.validateResponse(example.response, schema);
|
|
748
|
-
if (result.success && !result.value.isValid) {
|
|
749
|
-
errors.push({
|
|
750
|
-
path: `endpoint.${endpoint.path}.example.${example.name}.response`,
|
|
751
|
-
message: `Response example does not match schema: ${result.value.errors.map((e) => e.message).join(', ')}`,
|
|
752
|
-
code: 'EXAMPLE_VALIDATION_FAILED',
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
private async validateAgainstJsonSchema(
|
|
762
|
-
data: unknown,
|
|
763
|
-
schemaContent: string,
|
|
764
|
-
errors: SchemaError[]
|
|
765
|
-
): Promise<void> {
|
|
766
|
-
try {
|
|
767
|
-
const schema = JSON.parse(schemaContent);
|
|
768
|
-
|
|
769
|
-
// JSON Schema validation with type, required, constraints, and nested object/array support
|
|
770
|
-
this.basicTypeValidation(data, schema, '', errors);
|
|
771
|
-
} catch {
|
|
772
|
-
errors.push({
|
|
773
|
-
path: '',
|
|
774
|
-
keyword: 'parse',
|
|
775
|
-
message: 'Failed to parse JSON Schema',
|
|
776
|
-
params: {},
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
private async validateAgainstOpenAPISchema(
|
|
782
|
-
data: unknown,
|
|
783
|
-
schemaContent: string,
|
|
784
|
-
errors: SchemaError[]
|
|
785
|
-
): Promise<void> {
|
|
786
|
-
// OpenAPI schemas are a subset of JSON Schema
|
|
787
|
-
await this.validateAgainstJsonSchema(data, schemaContent, errors);
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
private async validateAgainstGraphQLSchema(
|
|
791
|
-
data: unknown,
|
|
792
|
-
schemaContent: string,
|
|
793
|
-
errors: SchemaError[]
|
|
794
|
-
): Promise<void> {
|
|
795
|
-
// Parse the GraphQL schema to extract type definitions
|
|
796
|
-
const schemaInfo = this.parseGraphQLSchema(schemaContent);
|
|
797
|
-
if (schemaInfo.errors.length > 0) {
|
|
798
|
-
for (const schemaError of schemaInfo.errors) {
|
|
799
|
-
errors.push({
|
|
800
|
-
path: '',
|
|
801
|
-
keyword: 'schema',
|
|
802
|
-
message: schemaError,
|
|
803
|
-
params: {},
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
// Validate the data (expected to be a GraphQL operation)
|
|
810
|
-
if (typeof data !== 'object' || data === null) {
|
|
811
|
-
errors.push({
|
|
812
|
-
path: '',
|
|
813
|
-
keyword: 'type',
|
|
814
|
-
message: 'GraphQL request must be an object with query/mutation',
|
|
815
|
-
params: {},
|
|
816
|
-
});
|
|
817
|
-
return;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const request = data as Record<string, unknown>;
|
|
821
|
-
const query = request.query as string | undefined;
|
|
822
|
-
const variables = request.variables as Record<string, unknown> | undefined;
|
|
823
|
-
|
|
824
|
-
if (!query || typeof query !== 'string') {
|
|
825
|
-
errors.push({
|
|
826
|
-
path: 'query',
|
|
827
|
-
keyword: 'required',
|
|
828
|
-
message: 'GraphQL request must contain a query string',
|
|
829
|
-
params: {},
|
|
830
|
-
});
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// Parse and validate the operation
|
|
835
|
-
const operationInfo = this.parseGraphQLOperation(query);
|
|
836
|
-
if (operationInfo.errors.length > 0) {
|
|
837
|
-
for (const opError of operationInfo.errors) {
|
|
838
|
-
errors.push({
|
|
839
|
-
path: 'query',
|
|
840
|
-
keyword: 'syntax',
|
|
841
|
-
message: opError,
|
|
842
|
-
params: {},
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// Validate operation type exists in schema
|
|
849
|
-
const operationType = operationInfo.type; // 'query' | 'mutation' | 'subscription'
|
|
850
|
-
const rootType = this.getRootTypeForOperation(operationType, schemaInfo);
|
|
851
|
-
if (!rootType) {
|
|
852
|
-
errors.push({
|
|
853
|
-
path: 'query',
|
|
854
|
-
keyword: 'operation',
|
|
855
|
-
message: `Schema does not support ${operationType} operations`,
|
|
856
|
-
params: { operationType },
|
|
857
|
-
});
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// Validate selected fields exist on the root type
|
|
862
|
-
for (const field of operationInfo.fields) {
|
|
863
|
-
const fieldDef = schemaInfo.types[rootType]?.fields[field.name];
|
|
864
|
-
if (!fieldDef) {
|
|
865
|
-
errors.push({
|
|
866
|
-
path: `query.${field.name}`,
|
|
867
|
-
keyword: 'field',
|
|
868
|
-
message: `Field '${field.name}' does not exist on type '${rootType}'`,
|
|
869
|
-
params: { field: field.name, type: rootType },
|
|
870
|
-
});
|
|
871
|
-
} else {
|
|
872
|
-
// Validate arguments if provided
|
|
873
|
-
if (field.arguments) {
|
|
874
|
-
for (const [argName, _argValue] of Object.entries(field.arguments)) {
|
|
875
|
-
if (!fieldDef.arguments?.[argName]) {
|
|
876
|
-
errors.push({
|
|
877
|
-
path: `query.${field.name}.${argName}`,
|
|
878
|
-
keyword: 'argument',
|
|
879
|
-
message: `Unknown argument '${argName}' on field '${field.name}'`,
|
|
880
|
-
params: { argument: argName, field: field.name },
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// Validate nested field selections
|
|
887
|
-
if (field.selections && field.selections.length > 0) {
|
|
888
|
-
const fieldType = this.unwrapGraphQLType(fieldDef.type);
|
|
889
|
-
this.validateGraphQLSelections(
|
|
890
|
-
field.selections,
|
|
891
|
-
fieldType,
|
|
892
|
-
schemaInfo,
|
|
893
|
-
`query.${field.name}`,
|
|
894
|
-
errors
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
// Validate variables against operation variable definitions
|
|
901
|
-
if (variables && operationInfo.variableDefinitions) {
|
|
902
|
-
for (const [varName, varDef] of Object.entries(operationInfo.variableDefinitions)) {
|
|
903
|
-
if (varDef.required && !(varName in variables)) {
|
|
904
|
-
errors.push({
|
|
905
|
-
path: `variables.${varName}`,
|
|
906
|
-
keyword: 'required',
|
|
907
|
-
message: `Required variable '${varName}' is missing`,
|
|
908
|
-
params: { variable: varName },
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
if (varName in variables) {
|
|
912
|
-
this.validateGraphQLVariableType(variables[varName], varDef.type, `variables.${varName}`, errors);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* Parse GraphQL schema to extract type definitions
|
|
920
|
-
*/
|
|
921
|
-
private parseGraphQLSchema(schemaContent: string): GraphQLSchemaInfo {
|
|
922
|
-
const info: GraphQLSchemaInfo = {
|
|
923
|
-
types: {},
|
|
924
|
-
queryType: null,
|
|
925
|
-
mutationType: null,
|
|
926
|
-
subscriptionType: null,
|
|
927
|
-
errors: [],
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
const lines = schemaContent.split('\n');
|
|
931
|
-
let currentType: string | null = null;
|
|
932
|
-
let currentTypeKind: 'type' | 'input' | 'interface' | 'enum' | null = null;
|
|
933
|
-
let braceDepth = 0;
|
|
934
|
-
|
|
935
|
-
for (let i = 0; i < lines.length; i++) {
|
|
936
|
-
const line = lines[i].trim();
|
|
937
|
-
|
|
938
|
-
// Skip comments and empty lines
|
|
939
|
-
if (line.startsWith('#') || line === '') continue;
|
|
940
|
-
|
|
941
|
-
// Detect schema definition
|
|
942
|
-
const schemaMatch = line.match(/^schema\s*\{?/);
|
|
943
|
-
if (schemaMatch) {
|
|
944
|
-
// Parse schema block to find root types
|
|
945
|
-
let schemaBlock = line;
|
|
946
|
-
if (!line.includes('}')) {
|
|
947
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
948
|
-
schemaBlock += ' ' + lines[j].trim();
|
|
949
|
-
if (lines[j].includes('}')) break;
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
const queryMatch = schemaBlock.match(/query\s*:\s*(\w+)/);
|
|
953
|
-
const mutationMatch = schemaBlock.match(/mutation\s*:\s*(\w+)/);
|
|
954
|
-
const subscriptionMatch = schemaBlock.match(/subscription\s*:\s*(\w+)/);
|
|
955
|
-
if (queryMatch) info.queryType = queryMatch[1];
|
|
956
|
-
if (mutationMatch) info.mutationType = mutationMatch[1];
|
|
957
|
-
if (subscriptionMatch) info.subscriptionType = subscriptionMatch[1];
|
|
958
|
-
continue;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// Detect type definitions
|
|
962
|
-
const typeMatch = line.match(/^(type|input|interface|enum)\s+(\w+)(?:\s+implements\s+(\w+))?\s*\{?/);
|
|
963
|
-
if (typeMatch) {
|
|
964
|
-
currentTypeKind = typeMatch[1] as 'type' | 'input' | 'interface' | 'enum';
|
|
965
|
-
currentType = typeMatch[2];
|
|
966
|
-
info.types[currentType] = {
|
|
967
|
-
kind: currentTypeKind,
|
|
968
|
-
fields: {},
|
|
969
|
-
implements: typeMatch[3] || null,
|
|
970
|
-
};
|
|
971
|
-
|
|
972
|
-
// Default Query/Mutation/Subscription types
|
|
973
|
-
if (currentType === 'Query' && !info.queryType) info.queryType = 'Query';
|
|
974
|
-
if (currentType === 'Mutation' && !info.mutationType) info.mutationType = 'Mutation';
|
|
975
|
-
if (currentType === 'Subscription' && !info.subscriptionType) info.subscriptionType = 'Subscription';
|
|
976
|
-
|
|
977
|
-
if (line.includes('{')) braceDepth++;
|
|
978
|
-
continue;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Track brace depth
|
|
982
|
-
if (line.includes('{')) braceDepth++;
|
|
983
|
-
if (line.includes('}')) {
|
|
984
|
-
braceDepth--;
|
|
985
|
-
if (braceDepth === 0) {
|
|
986
|
-
currentType = null;
|
|
987
|
-
currentTypeKind = null;
|
|
988
|
-
}
|
|
989
|
-
continue;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// Parse field definitions within a type
|
|
993
|
-
if (currentType && currentTypeKind !== 'enum') {
|
|
994
|
-
const fieldMatch = line.match(/^(\w+)(?:\s*\(([^)]*)\))?\s*:\s*(.+?)(?:\s*@.*)?$/);
|
|
995
|
-
if (fieldMatch) {
|
|
996
|
-
const fieldName = fieldMatch[1];
|
|
997
|
-
const argsStr = fieldMatch[2];
|
|
998
|
-
const fieldType = fieldMatch[3].trim();
|
|
999
|
-
|
|
1000
|
-
const fieldDef: GraphQLFieldDef = {
|
|
1001
|
-
type: fieldType,
|
|
1002
|
-
arguments: {},
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
// Parse arguments
|
|
1006
|
-
if (argsStr) {
|
|
1007
|
-
const argMatches = argsStr.matchAll(/(\w+)\s*:\s*([^,\)]+)/g);
|
|
1008
|
-
for (const argMatch of argMatches) {
|
|
1009
|
-
fieldDef.arguments![argMatch[1]] = { type: argMatch[2].trim() };
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
info.types[currentType].fields[fieldName] = fieldDef;
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Parse enum values
|
|
1018
|
-
if (currentType && currentTypeKind === 'enum') {
|
|
1019
|
-
const enumValue = line.match(/^(\w+)(?:\s*@.*)?$/);
|
|
1020
|
-
if (enumValue) {
|
|
1021
|
-
info.types[currentType].fields[enumValue[1]] = { type: 'EnumValue' };
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
// Validate schema has at least a query type
|
|
1027
|
-
if (!info.queryType && Object.keys(info.types).length > 0) {
|
|
1028
|
-
// Check if there's a Query type defined
|
|
1029
|
-
if (!info.types['Query']) {
|
|
1030
|
-
info.errors.push('GraphQL schema must define a Query type or schema definition');
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
return info;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
/**
|
|
1038
|
-
* Parse GraphQL operation (query/mutation/subscription)
|
|
1039
|
-
*/
|
|
1040
|
-
private parseGraphQLOperation(query: string): GraphQLOperationInfo {
|
|
1041
|
-
const info: GraphQLOperationInfo = {
|
|
1042
|
-
type: 'query',
|
|
1043
|
-
name: null,
|
|
1044
|
-
fields: [],
|
|
1045
|
-
variableDefinitions: {},
|
|
1046
|
-
errors: [],
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
// Remove comments
|
|
1050
|
-
const cleanQuery = query.replace(/#[^\n]*/g, '').trim();
|
|
1051
|
-
|
|
1052
|
-
// Detect operation type
|
|
1053
|
-
const operationMatch = cleanQuery.match(/^(query|mutation|subscription)(?:\s+(\w+))?\s*(?:\(([^)]*)\))?\s*\{/);
|
|
1054
|
-
if (operationMatch) {
|
|
1055
|
-
info.type = operationMatch[1] as 'query' | 'mutation' | 'subscription';
|
|
1056
|
-
info.name = operationMatch[2] || null;
|
|
1057
|
-
|
|
1058
|
-
// Parse variable definitions
|
|
1059
|
-
if (operationMatch[3]) {
|
|
1060
|
-
const varMatches = operationMatch[3].matchAll(/\$(\w+)\s*:\s*([^,\)!]+)(!)?/g);
|
|
1061
|
-
for (const varMatch of varMatches) {
|
|
1062
|
-
info.variableDefinitions[varMatch[1]] = {
|
|
1063
|
-
type: varMatch[2].trim(),
|
|
1064
|
-
required: !!varMatch[3],
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
} else if (cleanQuery.startsWith('{')) {
|
|
1069
|
-
// Anonymous query
|
|
1070
|
-
info.type = 'query';
|
|
1071
|
-
} else if (!cleanQuery.includes('{')) {
|
|
1072
|
-
info.errors.push('Invalid GraphQL operation: missing selection set');
|
|
1073
|
-
return info;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// Extract the selection set
|
|
1077
|
-
const selectionSetMatch = cleanQuery.match(/\{([\s\S]*)\}/);
|
|
1078
|
-
if (!selectionSetMatch) {
|
|
1079
|
-
info.errors.push('Invalid GraphQL operation: could not parse selection set');
|
|
1080
|
-
return info;
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// Parse fields from selection set
|
|
1084
|
-
info.fields = this.parseGraphQLSelectionSet(selectionSetMatch[1]);
|
|
1085
|
-
|
|
1086
|
-
return info;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
/**
|
|
1090
|
-
* Parse GraphQL selection set into field list
|
|
1091
|
-
*/
|
|
1092
|
-
private parseGraphQLSelectionSet(selectionSet: string): GraphQLField[] {
|
|
1093
|
-
const fields: GraphQLField[] = [];
|
|
1094
|
-
let depth = 0;
|
|
1095
|
-
let currentField = '';
|
|
1096
|
-
let i = 0;
|
|
1097
|
-
|
|
1098
|
-
while (i < selectionSet.length) {
|
|
1099
|
-
const char = selectionSet[i];
|
|
1100
|
-
|
|
1101
|
-
if (char === '{') {
|
|
1102
|
-
depth++;
|
|
1103
|
-
currentField += char;
|
|
1104
|
-
} else if (char === '}') {
|
|
1105
|
-
depth--;
|
|
1106
|
-
currentField += char;
|
|
1107
|
-
} else if ((char === '\n' || char === ' ' || char === ',') && depth === 0) {
|
|
1108
|
-
if (currentField.trim()) {
|
|
1109
|
-
const field = this.parseGraphQLField(currentField.trim());
|
|
1110
|
-
if (field) fields.push(field);
|
|
1111
|
-
}
|
|
1112
|
-
currentField = '';
|
|
1113
|
-
} else {
|
|
1114
|
-
currentField += char;
|
|
1115
|
-
}
|
|
1116
|
-
i++;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
// Handle last field
|
|
1120
|
-
if (currentField.trim()) {
|
|
1121
|
-
const field = this.parseGraphQLField(currentField.trim());
|
|
1122
|
-
if (field) fields.push(field);
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
return fields;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
/**
|
|
1129
|
-
* Parse a single GraphQL field
|
|
1130
|
-
*/
|
|
1131
|
-
private parseGraphQLField(fieldStr: string): GraphQLField | null {
|
|
1132
|
-
// Handle alias
|
|
1133
|
-
const aliasMatch = fieldStr.match(/^(\w+)\s*:\s*(.+)$/);
|
|
1134
|
-
let fieldContent = fieldStr;
|
|
1135
|
-
let alias: string | undefined;
|
|
1136
|
-
if (aliasMatch && !aliasMatch[2].includes('(') && !aliasMatch[2].includes('{')) {
|
|
1137
|
-
// This might be a type annotation in variable, not an alias - skip
|
|
1138
|
-
} else if (aliasMatch && (aliasMatch[2].match(/^\w/) || aliasMatch[2].includes('('))) {
|
|
1139
|
-
alias = aliasMatch[1];
|
|
1140
|
-
fieldContent = aliasMatch[2];
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
// Parse field name and arguments
|
|
1144
|
-
const fieldMatch = fieldContent.match(/^(\w+)(?:\s*\(([^)]*)\))?(?:\s*\{([\s\S]*)\})?$/);
|
|
1145
|
-
if (!fieldMatch) {
|
|
1146
|
-
// Try without nested selection
|
|
1147
|
-
const simpleMatch = fieldContent.match(/^(\w+)(?:\s*\(([^)]*)\))?/);
|
|
1148
|
-
if (simpleMatch) {
|
|
1149
|
-
return {
|
|
1150
|
-
name: simpleMatch[1],
|
|
1151
|
-
alias,
|
|
1152
|
-
arguments: simpleMatch[2] ? this.parseGraphQLArguments(simpleMatch[2]) : undefined,
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
return null;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
const field: GraphQLField = {
|
|
1159
|
-
name: fieldMatch[1],
|
|
1160
|
-
alias,
|
|
1161
|
-
};
|
|
1162
|
-
|
|
1163
|
-
// Parse arguments
|
|
1164
|
-
if (fieldMatch[2]) {
|
|
1165
|
-
field.arguments = this.parseGraphQLArguments(fieldMatch[2]);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
// Parse nested selections
|
|
1169
|
-
if (fieldMatch[3]) {
|
|
1170
|
-
field.selections = this.parseGraphQLSelectionSet(fieldMatch[3]);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
return field;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
/**
|
|
1177
|
-
* Parse GraphQL arguments
|
|
1178
|
-
*/
|
|
1179
|
-
private parseGraphQLArguments(argsStr: string): Record<string, unknown> {
|
|
1180
|
-
const args: Record<string, unknown> = {};
|
|
1181
|
-
const argMatches = argsStr.matchAll(/(\w+)\s*:\s*([^,\)]+)/g);
|
|
1182
|
-
for (const match of argMatches) {
|
|
1183
|
-
const value = match[2].trim();
|
|
1184
|
-
// Parse value (handle variables, strings, numbers, booleans)
|
|
1185
|
-
if (value.startsWith('$')) {
|
|
1186
|
-
args[match[1]] = { _variable: value.substring(1) };
|
|
1187
|
-
} else if (value.startsWith('"') || value.startsWith("'")) {
|
|
1188
|
-
args[match[1]] = value.slice(1, -1);
|
|
1189
|
-
} else if (value === 'true') {
|
|
1190
|
-
args[match[1]] = true;
|
|
1191
|
-
} else if (value === 'false') {
|
|
1192
|
-
args[match[1]] = false;
|
|
1193
|
-
} else if (value === 'null') {
|
|
1194
|
-
args[match[1]] = null;
|
|
1195
|
-
} else if (!isNaN(Number(value))) {
|
|
1196
|
-
args[match[1]] = Number(value);
|
|
1197
|
-
} else {
|
|
1198
|
-
args[match[1]] = value; // Enum value
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
return args;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
* Get root type name for operation type
|
|
1206
|
-
*/
|
|
1207
|
-
private getRootTypeForOperation(
|
|
1208
|
-
operationType: 'query' | 'mutation' | 'subscription',
|
|
1209
|
-
schemaInfo: GraphQLSchemaInfo
|
|
1210
|
-
): string | null {
|
|
1211
|
-
switch (operationType) {
|
|
1212
|
-
case 'query':
|
|
1213
|
-
return schemaInfo.queryType;
|
|
1214
|
-
case 'mutation':
|
|
1215
|
-
return schemaInfo.mutationType;
|
|
1216
|
-
case 'subscription':
|
|
1217
|
-
return schemaInfo.subscriptionType;
|
|
1218
|
-
default:
|
|
1219
|
-
return null;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
/**
|
|
1224
|
-
* Unwrap GraphQL type (remove [], !)
|
|
1225
|
-
*/
|
|
1226
|
-
private unwrapGraphQLType(type: string): string {
|
|
1227
|
-
return type.replace(/[\[\]!]/g, '').trim();
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* Validate nested field selections
|
|
1232
|
-
*/
|
|
1233
|
-
private validateGraphQLSelections(
|
|
1234
|
-
selections: GraphQLField[],
|
|
1235
|
-
typeName: string,
|
|
1236
|
-
schemaInfo: GraphQLSchemaInfo,
|
|
1237
|
-
path: string,
|
|
1238
|
-
errors: SchemaError[]
|
|
1239
|
-
): void {
|
|
1240
|
-
const typeDef = schemaInfo.types[typeName];
|
|
1241
|
-
if (!typeDef) {
|
|
1242
|
-
// Could be a scalar type, skip validation
|
|
1243
|
-
if (['String', 'Int', 'Float', 'Boolean', 'ID'].includes(typeName)) {
|
|
1244
|
-
if (selections.length > 0) {
|
|
1245
|
-
errors.push({
|
|
1246
|
-
path,
|
|
1247
|
-
keyword: 'selection',
|
|
1248
|
-
message: `Cannot select fields on scalar type '${typeName}'`,
|
|
1249
|
-
params: { type: typeName },
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
return;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
for (const selection of selections) {
|
|
1257
|
-
const fieldDef = typeDef.fields[selection.name];
|
|
1258
|
-
if (!fieldDef) {
|
|
1259
|
-
errors.push({
|
|
1260
|
-
path: `${path}.${selection.name}`,
|
|
1261
|
-
keyword: 'field',
|
|
1262
|
-
message: `Field '${selection.name}' does not exist on type '${typeName}'`,
|
|
1263
|
-
params: { field: selection.name, type: typeName },
|
|
1264
|
-
});
|
|
1265
|
-
} else if (selection.selections && selection.selections.length > 0) {
|
|
1266
|
-
const nestedType = this.unwrapGraphQLType(fieldDef.type);
|
|
1267
|
-
this.validateGraphQLSelections(
|
|
1268
|
-
selection.selections,
|
|
1269
|
-
nestedType,
|
|
1270
|
-
schemaInfo,
|
|
1271
|
-
`${path}.${selection.name}`,
|
|
1272
|
-
errors
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
/**
|
|
1279
|
-
* Validate variable type matches expected GraphQL type
|
|
1280
|
-
*/
|
|
1281
|
-
private validateGraphQLVariableType(
|
|
1282
|
-
value: unknown,
|
|
1283
|
-
expectedType: string,
|
|
1284
|
-
path: string,
|
|
1285
|
-
errors: SchemaError[]
|
|
1286
|
-
): void {
|
|
1287
|
-
const baseType = this.unwrapGraphQLType(expectedType);
|
|
1288
|
-
const isNonNull = expectedType.endsWith('!');
|
|
1289
|
-
const isList = expectedType.includes('[');
|
|
1290
|
-
|
|
1291
|
-
if (value === null || value === undefined) {
|
|
1292
|
-
if (isNonNull) {
|
|
1293
|
-
errors.push({
|
|
1294
|
-
path,
|
|
1295
|
-
keyword: 'type',
|
|
1296
|
-
message: `Variable cannot be null (expected ${expectedType})`,
|
|
1297
|
-
params: { expectedType },
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
if (isList) {
|
|
1304
|
-
if (!Array.isArray(value)) {
|
|
1305
|
-
errors.push({
|
|
1306
|
-
path,
|
|
1307
|
-
keyword: 'type',
|
|
1308
|
-
message: `Expected array for type ${expectedType}`,
|
|
1309
|
-
params: { expectedType, actualType: typeof value },
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
// Validate scalar types
|
|
1316
|
-
switch (baseType) {
|
|
1317
|
-
case 'String':
|
|
1318
|
-
case 'ID':
|
|
1319
|
-
if (typeof value !== 'string') {
|
|
1320
|
-
errors.push({
|
|
1321
|
-
path,
|
|
1322
|
-
keyword: 'type',
|
|
1323
|
-
message: `Expected string for type ${baseType}`,
|
|
1324
|
-
params: { expectedType: baseType, actualType: typeof value },
|
|
1325
|
-
});
|
|
1326
|
-
}
|
|
1327
|
-
break;
|
|
1328
|
-
case 'Int':
|
|
1329
|
-
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
1330
|
-
errors.push({
|
|
1331
|
-
path,
|
|
1332
|
-
keyword: 'type',
|
|
1333
|
-
message: `Expected integer for type Int`,
|
|
1334
|
-
params: { expectedType: 'Int', actualType: typeof value },
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
break;
|
|
1338
|
-
case 'Float':
|
|
1339
|
-
if (typeof value !== 'number') {
|
|
1340
|
-
errors.push({
|
|
1341
|
-
path,
|
|
1342
|
-
keyword: 'type',
|
|
1343
|
-
message: `Expected number for type Float`,
|
|
1344
|
-
params: { expectedType: 'Float', actualType: typeof value },
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
break;
|
|
1348
|
-
case 'Boolean':
|
|
1349
|
-
if (typeof value !== 'boolean') {
|
|
1350
|
-
errors.push({
|
|
1351
|
-
path,
|
|
1352
|
-
keyword: 'type',
|
|
1353
|
-
message: `Expected boolean for type Boolean`,
|
|
1354
|
-
params: { expectedType: 'Boolean', actualType: typeof value },
|
|
1355
|
-
});
|
|
1356
|
-
}
|
|
1357
|
-
break;
|
|
1358
|
-
// Custom types - basic object validation
|
|
1359
|
-
default:
|
|
1360
|
-
if (typeof value !== 'object') {
|
|
1361
|
-
errors.push({
|
|
1362
|
-
path,
|
|
1363
|
-
keyword: 'type',
|
|
1364
|
-
message: `Expected object for type ${baseType}`,
|
|
1365
|
-
params: { expectedType: baseType, actualType: typeof value },
|
|
1366
|
-
});
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
private basicTypeValidation(
|
|
1372
|
-
data: unknown,
|
|
1373
|
-
schema: Record<string, unknown>,
|
|
1374
|
-
path: string,
|
|
1375
|
-
errors: SchemaError[],
|
|
1376
|
-
depth: number = 0
|
|
1377
|
-
): void {
|
|
1378
|
-
if (depth > this.config.maxSchemaDepth) {
|
|
1379
|
-
errors.push({
|
|
1380
|
-
path,
|
|
1381
|
-
keyword: 'maxDepth',
|
|
1382
|
-
message: 'Maximum schema depth exceeded',
|
|
1383
|
-
params: { maxDepth: this.config.maxSchemaDepth },
|
|
1384
|
-
});
|
|
1385
|
-
return;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
const type = schema.type as string | undefined;
|
|
1389
|
-
if (!type) return;
|
|
1390
|
-
|
|
1391
|
-
const actualType = this.getJsonType(data);
|
|
1392
|
-
|
|
1393
|
-
if (type === 'object' && actualType === 'object') {
|
|
1394
|
-
const properties = (schema.properties as Record<string, unknown>) || {};
|
|
1395
|
-
const required = (schema.required as string[]) || [];
|
|
1396
|
-
const dataObj = data as Record<string, unknown>;
|
|
1397
|
-
|
|
1398
|
-
// Check required properties
|
|
1399
|
-
for (const prop of required) {
|
|
1400
|
-
if (!(prop in dataObj)) {
|
|
1401
|
-
errors.push({
|
|
1402
|
-
path: path ? `${path}.${prop}` : prop,
|
|
1403
|
-
keyword: 'required',
|
|
1404
|
-
message: `Required property '${prop}' is missing`,
|
|
1405
|
-
params: { missingProperty: prop },
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
// Validate each property
|
|
1411
|
-
for (const [prop, propSchema] of Object.entries(properties)) {
|
|
1412
|
-
if (prop in dataObj) {
|
|
1413
|
-
this.basicTypeValidation(
|
|
1414
|
-
dataObj[prop],
|
|
1415
|
-
propSchema as Record<string, unknown>,
|
|
1416
|
-
path ? `${path}.${prop}` : prop,
|
|
1417
|
-
errors,
|
|
1418
|
-
depth + 1
|
|
1419
|
-
);
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
} else if (type === 'array' && actualType === 'array') {
|
|
1423
|
-
const items = schema.items as Record<string, unknown> | undefined;
|
|
1424
|
-
const dataArr = data as unknown[];
|
|
1425
|
-
|
|
1426
|
-
// Validate array constraints
|
|
1427
|
-
this.validateArrayConstraints(dataArr, schema, path, errors);
|
|
1428
|
-
|
|
1429
|
-
// Validate each item
|
|
1430
|
-
if (items) {
|
|
1431
|
-
for (let i = 0; i < dataArr.length; i++) {
|
|
1432
|
-
this.basicTypeValidation(
|
|
1433
|
-
dataArr[i],
|
|
1434
|
-
items,
|
|
1435
|
-
`${path}[${i}]`,
|
|
1436
|
-
errors,
|
|
1437
|
-
depth + 1
|
|
1438
|
-
);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
} else if (type === 'string' && actualType === 'string') {
|
|
1442
|
-
// Validate string constraints
|
|
1443
|
-
this.validateStringConstraints(data as string, schema, path, errors);
|
|
1444
|
-
} else if ((type === 'number' || type === 'integer') && (actualType === 'number' || actualType === 'integer')) {
|
|
1445
|
-
// Validate number constraints
|
|
1446
|
-
this.validateNumberConstraints(data as number, schema, path, errors);
|
|
1447
|
-
} else if (type !== actualType) {
|
|
1448
|
-
// Allow integer as number
|
|
1449
|
-
if (!(type === 'number' && actualType === 'integer')) {
|
|
1450
|
-
errors.push({
|
|
1451
|
-
path,
|
|
1452
|
-
keyword: 'type',
|
|
1453
|
-
message: `Expected type '${type}' but got '${actualType}'`,
|
|
1454
|
-
params: { expectedType: type, actualType },
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
// Check enum constraint (applies to any type)
|
|
1460
|
-
if (schema.enum) {
|
|
1461
|
-
const enumValues = schema.enum as unknown[];
|
|
1462
|
-
if (!enumValues.some((v) => JSON.stringify(v) === JSON.stringify(data))) {
|
|
1463
|
-
errors.push({
|
|
1464
|
-
path,
|
|
1465
|
-
keyword: 'enum',
|
|
1466
|
-
message: `Value must be one of: ${enumValues.map((v) => JSON.stringify(v)).join(', ')}`,
|
|
1467
|
-
params: { allowedValues: enumValues },
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
private validateStringConstraints(
|
|
1474
|
-
data: string,
|
|
1475
|
-
schema: Record<string, unknown>,
|
|
1476
|
-
path: string,
|
|
1477
|
-
errors: SchemaError[]
|
|
1478
|
-
): void {
|
|
1479
|
-
const minLength = schema.minLength as number | undefined;
|
|
1480
|
-
const maxLength = schema.maxLength as number | undefined;
|
|
1481
|
-
const pattern = schema.pattern as string | undefined;
|
|
1482
|
-
|
|
1483
|
-
if (minLength !== undefined && data.length < minLength) {
|
|
1484
|
-
errors.push({
|
|
1485
|
-
path,
|
|
1486
|
-
keyword: 'minLength',
|
|
1487
|
-
message: `String must be at least ${minLength} characters`,
|
|
1488
|
-
params: { limit: minLength, actual: data.length },
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
if (maxLength !== undefined && data.length > maxLength) {
|
|
1493
|
-
errors.push({
|
|
1494
|
-
path,
|
|
1495
|
-
keyword: 'maxLength',
|
|
1496
|
-
message: `String must be at most ${maxLength} characters`,
|
|
1497
|
-
params: { limit: maxLength, actual: data.length },
|
|
1498
|
-
});
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
if (pattern) {
|
|
1502
|
-
try {
|
|
1503
|
-
const regex = new RegExp(pattern);
|
|
1504
|
-
if (!regex.test(data)) {
|
|
1505
|
-
errors.push({
|
|
1506
|
-
path,
|
|
1507
|
-
keyword: 'pattern',
|
|
1508
|
-
message: `String must match pattern: ${pattern}`,
|
|
1509
|
-
params: { pattern },
|
|
1510
|
-
});
|
|
1511
|
-
}
|
|
1512
|
-
} catch {
|
|
1513
|
-
// Invalid regex pattern, skip validation
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
private validateNumberConstraints(
|
|
1519
|
-
data: number,
|
|
1520
|
-
schema: Record<string, unknown>,
|
|
1521
|
-
path: string,
|
|
1522
|
-
errors: SchemaError[]
|
|
1523
|
-
): void {
|
|
1524
|
-
const minimum = schema.minimum as number | undefined;
|
|
1525
|
-
const maximum = schema.maximum as number | undefined;
|
|
1526
|
-
const exclusiveMinimum = schema.exclusiveMinimum as number | undefined;
|
|
1527
|
-
const exclusiveMaximum = schema.exclusiveMaximum as number | undefined;
|
|
1528
|
-
const multipleOf = schema.multipleOf as number | undefined;
|
|
1529
|
-
|
|
1530
|
-
if (minimum !== undefined && data < minimum) {
|
|
1531
|
-
errors.push({
|
|
1532
|
-
path,
|
|
1533
|
-
keyword: 'minimum',
|
|
1534
|
-
message: `Number must be >= ${minimum}`,
|
|
1535
|
-
params: { limit: minimum, actual: data },
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
if (maximum !== undefined && data > maximum) {
|
|
1540
|
-
errors.push({
|
|
1541
|
-
path,
|
|
1542
|
-
keyword: 'maximum',
|
|
1543
|
-
message: `Number must be <= ${maximum}`,
|
|
1544
|
-
params: { limit: maximum, actual: data },
|
|
1545
|
-
});
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
if (exclusiveMinimum !== undefined && data <= exclusiveMinimum) {
|
|
1549
|
-
errors.push({
|
|
1550
|
-
path,
|
|
1551
|
-
keyword: 'exclusiveMinimum',
|
|
1552
|
-
message: `Number must be > ${exclusiveMinimum}`,
|
|
1553
|
-
params: { limit: exclusiveMinimum, actual: data },
|
|
1554
|
-
});
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
if (exclusiveMaximum !== undefined && data >= exclusiveMaximum) {
|
|
1558
|
-
errors.push({
|
|
1559
|
-
path,
|
|
1560
|
-
keyword: 'exclusiveMaximum',
|
|
1561
|
-
message: `Number must be < ${exclusiveMaximum}`,
|
|
1562
|
-
params: { limit: exclusiveMaximum, actual: data },
|
|
1563
|
-
});
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
if (multipleOf !== undefined && data % multipleOf !== 0) {
|
|
1567
|
-
errors.push({
|
|
1568
|
-
path,
|
|
1569
|
-
keyword: 'multipleOf',
|
|
1570
|
-
message: `Number must be a multiple of ${multipleOf}`,
|
|
1571
|
-
params: { multipleOf, actual: data },
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
private validateArrayConstraints(
|
|
1577
|
-
data: unknown[],
|
|
1578
|
-
schema: Record<string, unknown>,
|
|
1579
|
-
path: string,
|
|
1580
|
-
errors: SchemaError[]
|
|
1581
|
-
): void {
|
|
1582
|
-
const minItems = schema.minItems as number | undefined;
|
|
1583
|
-
const maxItems = schema.maxItems as number | undefined;
|
|
1584
|
-
const uniqueItems = schema.uniqueItems as boolean | undefined;
|
|
1585
|
-
|
|
1586
|
-
if (minItems !== undefined && data.length < minItems) {
|
|
1587
|
-
errors.push({
|
|
1588
|
-
path,
|
|
1589
|
-
keyword: 'minItems',
|
|
1590
|
-
message: `Array must have at least ${minItems} items`,
|
|
1591
|
-
params: { limit: minItems, actual: data.length },
|
|
1592
|
-
});
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
if (maxItems !== undefined && data.length > maxItems) {
|
|
1596
|
-
errors.push({
|
|
1597
|
-
path,
|
|
1598
|
-
keyword: 'maxItems',
|
|
1599
|
-
message: `Array must have at most ${maxItems} items`,
|
|
1600
|
-
params: { limit: maxItems, actual: data.length },
|
|
1601
|
-
});
|
|
1602
|
-
}
|
|
1603
|
-
|
|
1604
|
-
if (uniqueItems) {
|
|
1605
|
-
const seen = new Set<string>();
|
|
1606
|
-
for (let i = 0; i < data.length; i++) {
|
|
1607
|
-
const serialized = JSON.stringify(data[i]);
|
|
1608
|
-
if (seen.has(serialized)) {
|
|
1609
|
-
errors.push({
|
|
1610
|
-
path: `${path}[${i}]`,
|
|
1611
|
-
keyword: 'uniqueItems',
|
|
1612
|
-
message: 'Array items must be unique',
|
|
1613
|
-
params: { duplicateIndex: i },
|
|
1614
|
-
});
|
|
1615
|
-
break;
|
|
1616
|
-
}
|
|
1617
|
-
seen.add(serialized);
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
private getJsonType(value: unknown): string {
|
|
1623
|
-
if (value === null) return 'null';
|
|
1624
|
-
if (Array.isArray(value)) return 'array';
|
|
1625
|
-
if (typeof value === 'number') {
|
|
1626
|
-
return Number.isInteger(value) ? 'integer' : 'number';
|
|
1627
|
-
}
|
|
1628
|
-
return typeof value;
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
private validateOpenAPI3Structure(
|
|
1632
|
-
spec: Record<string, unknown>,
|
|
1633
|
-
errors: ValidationError[],
|
|
1634
|
-
warnings: string[]
|
|
1635
|
-
): void {
|
|
1636
|
-
// Required fields for OpenAPI 3.x
|
|
1637
|
-
if (!spec.info) {
|
|
1638
|
-
errors.push({
|
|
1639
|
-
path: 'info',
|
|
1640
|
-
message: 'OpenAPI 3.x requires info object',
|
|
1641
|
-
code: 'REQUIRED_FIELD',
|
|
1642
|
-
});
|
|
1643
|
-
} else {
|
|
1644
|
-
const info = spec.info as Record<string, unknown>;
|
|
1645
|
-
if (!info.title) {
|
|
1646
|
-
errors.push({
|
|
1647
|
-
path: 'info.title',
|
|
1648
|
-
message: 'API title is required',
|
|
1649
|
-
code: 'REQUIRED_FIELD',
|
|
1650
|
-
});
|
|
1651
|
-
}
|
|
1652
|
-
if (!info.version) {
|
|
1653
|
-
errors.push({
|
|
1654
|
-
path: 'info.version',
|
|
1655
|
-
message: 'API version is required',
|
|
1656
|
-
code: 'REQUIRED_FIELD',
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
if (!spec.paths) {
|
|
1662
|
-
warnings.push('No paths defined in OpenAPI specification');
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
private validateSwagger2Structure(
|
|
1667
|
-
spec: Record<string, unknown>,
|
|
1668
|
-
errors: ValidationError[],
|
|
1669
|
-
warnings: string[]
|
|
1670
|
-
): void {
|
|
1671
|
-
// Required fields for Swagger 2
|
|
1672
|
-
if (!spec.info) {
|
|
1673
|
-
errors.push({
|
|
1674
|
-
path: 'info',
|
|
1675
|
-
message: 'Swagger 2 requires info object',
|
|
1676
|
-
code: 'REQUIRED_FIELD',
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1680
|
-
if (!spec.paths) {
|
|
1681
|
-
warnings.push('No paths defined in Swagger specification');
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
// Swagger 2 requires host or basePath for some operations
|
|
1685
|
-
if (!spec.host && !spec.basePath) {
|
|
1686
|
-
warnings.push('No host or basePath defined in Swagger specification');
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
private async storeValidationHistory(
|
|
1691
|
-
contractId: string,
|
|
1692
|
-
report: ValidationReport
|
|
1693
|
-
): Promise<void> {
|
|
1694
|
-
const historyKey = `contract-testing:validation:${contractId}:${Date.now()}`;
|
|
1695
|
-
await this.memory.set(historyKey, report, {
|
|
1696
|
-
namespace: 'contract-testing',
|
|
1697
|
-
ttl: 86400 * 30, // 30 days
|
|
1698
|
-
});
|
|
1699
|
-
}
|
|
1700
|
-
}
|