reqon-dsl 0.2.0
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/.claude/settings.local.json +31 -0
- package/.claude/skills/api-integration.md +125 -0
- package/.claude/skills/database-schema.md +51 -0
- package/.claude/skills/dsl-design.md +80 -0
- package/.claude/skills/property-testing.md +143 -0
- package/.claude/skills/reqon/SKILL.md +44 -0
- package/.claude/skills/reqon/references/examples.md +206 -0
- package/.claude/skills/reqon/references/syntax.md +263 -0
- package/.claude/skills/vscode-extension.md +113 -0
- package/.github/dependabot.yml +32 -0
- package/.github/pull_request_template.md +21 -0
- package/.github/workflows/ci.yml +174 -0
- package/.github/workflows/release.yml +73 -0
- package/CLAUDE.md +72 -0
- package/CONTRIBUTING.md +161 -0
- package/README.md +235 -0
- package/TODO.md +51 -0
- package/dist/ast/index.d.ts +1 -0
- package/dist/ast/index.js +1 -0
- package/dist/ast/nodes.d.ts +237 -0
- package/dist/ast/nodes.js +12 -0
- package/dist/auth/auth.test.d.ts +1 -0
- package/dist/auth/auth.test.js +255 -0
- package/dist/auth/circuit-breaker.d.ts +115 -0
- package/dist/auth/circuit-breaker.js +267 -0
- package/dist/auth/credentials.d.ts +91 -0
- package/dist/auth/credentials.js +169 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/oauth2-provider.d.ts +41 -0
- package/dist/auth/oauth2-provider.js +131 -0
- package/dist/auth/rate-limiter.d.ts +61 -0
- package/dist/auth/rate-limiter.js +380 -0
- package/dist/auth/token-store.d.ts +30 -0
- package/dist/auth/token-store.js +148 -0
- package/dist/auth/types.d.ts +142 -0
- package/dist/auth/types.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +270 -0
- package/dist/errors/errors.test.d.ts +1 -0
- package/dist/errors/errors.test.js +165 -0
- package/dist/errors/index.d.ts +83 -0
- package/dist/errors/index.js +159 -0
- package/dist/execution/execution.test.d.ts +1 -0
- package/dist/execution/execution.test.js +246 -0
- package/dist/execution/index.d.ts +4 -0
- package/dist/execution/index.js +2 -0
- package/dist/execution/state.d.ts +136 -0
- package/dist/execution/state.js +82 -0
- package/dist/execution/store.d.ts +52 -0
- package/dist/execution/store.js +120 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +57 -0
- package/dist/integration.test.d.ts +1 -0
- package/dist/integration.test.js +168 -0
- package/dist/interpreter/context.d.ts +15 -0
- package/dist/interpreter/context.js +29 -0
- package/dist/interpreter/evaluator.d.ts +5 -0
- package/dist/interpreter/evaluator.js +223 -0
- package/dist/interpreter/evaluator.test.d.ts +1 -0
- package/dist/interpreter/evaluator.test.js +512 -0
- package/dist/interpreter/executor.d.ts +131 -0
- package/dist/interpreter/executor.js +663 -0
- package/dist/interpreter/fetch-handler.d.ts +43 -0
- package/dist/interpreter/fetch-handler.js +203 -0
- package/dist/interpreter/http.d.ts +57 -0
- package/dist/interpreter/http.js +210 -0
- package/dist/interpreter/http.test.d.ts +1 -0
- package/dist/interpreter/http.test.js +299 -0
- package/dist/interpreter/index.d.ts +7 -0
- package/dist/interpreter/index.js +7 -0
- package/dist/interpreter/pagination.d.ts +63 -0
- package/dist/interpreter/pagination.js +155 -0
- package/dist/interpreter/progress.test.d.ts +1 -0
- package/dist/interpreter/progress.test.js +216 -0
- package/dist/interpreter/schema-matcher.d.ts +16 -0
- package/dist/interpreter/schema-matcher.js +136 -0
- package/dist/interpreter/schema-matcher.test.d.ts +1 -0
- package/dist/interpreter/schema-matcher.test.js +122 -0
- package/dist/interpreter/signals.d.ts +57 -0
- package/dist/interpreter/signals.js +73 -0
- package/dist/interpreter/step-handlers/for-handler.d.ts +17 -0
- package/dist/interpreter/step-handlers/for-handler.js +51 -0
- package/dist/interpreter/step-handlers/index.d.ts +8 -0
- package/dist/interpreter/step-handlers/index.js +8 -0
- package/dist/interpreter/step-handlers/map-handler.d.ts +10 -0
- package/dist/interpreter/step-handlers/map-handler.js +20 -0
- package/dist/interpreter/step-handlers/match-handler.d.ts +27 -0
- package/dist/interpreter/step-handlers/match-handler.js +61 -0
- package/dist/interpreter/step-handlers/store-handler.d.ts +13 -0
- package/dist/interpreter/step-handlers/store-handler.js +66 -0
- package/dist/interpreter/step-handlers/types.d.ts +15 -0
- package/dist/interpreter/step-handlers/types.js +1 -0
- package/dist/interpreter/step-handlers/validate-handler.d.ts +10 -0
- package/dist/interpreter/step-handlers/validate-handler.js +26 -0
- package/dist/interpreter/step-handlers/webhook-handler.d.ts +36 -0
- package/dist/interpreter/step-handlers/webhook-handler.js +104 -0
- package/dist/lexer/index.d.ts +10 -0
- package/dist/lexer/index.js +12 -0
- package/dist/lexer/lexer.d.ts +24 -0
- package/dist/lexer/lexer.js +264 -0
- package/dist/lexer/lexer.test.d.ts +1 -0
- package/dist/lexer/lexer.test.js +259 -0
- package/dist/lexer/tokens.d.ts +69 -0
- package/dist/lexer/tokens.js +146 -0
- package/dist/loader/index.d.ts +36 -0
- package/dist/loader/index.js +220 -0
- package/dist/loader/loader.test.d.ts +1 -0
- package/dist/loader/loader.test.js +287 -0
- package/dist/oas/index.d.ts +4 -0
- package/dist/oas/index.js +2 -0
- package/dist/oas/loader.d.ts +21 -0
- package/dist/oas/loader.js +82 -0
- package/dist/oas/oas.test.d.ts +1 -0
- package/dist/oas/oas.test.js +218 -0
- package/dist/oas/validator.d.ts +12 -0
- package/dist/oas/validator.js +227 -0
- package/dist/parser/base.d.ts +33 -0
- package/dist/parser/base.js +97 -0
- package/dist/parser/expressions.d.ts +27 -0
- package/dist/parser/expressions.js +248 -0
- package/dist/parser/expressions.test.d.ts +1 -0
- package/dist/parser/expressions.test.js +378 -0
- package/dist/parser/index.d.ts +3 -0
- package/dist/parser/index.js +3 -0
- package/dist/parser/match.test.d.ts +1 -0
- package/dist/parser/match.test.js +254 -0
- package/dist/parser/parser.d.ts +68 -0
- package/dist/parser/parser.js +1229 -0
- package/dist/parser/parser.test.d.ts +1 -0
- package/dist/parser/parser.test.js +333 -0
- package/dist/parser/schedule.test.d.ts +1 -0
- package/dist/parser/schedule.test.js +241 -0
- package/dist/plugin.d.ts +35 -0
- package/dist/plugin.js +68 -0
- package/dist/scheduler/cron-parser.d.ts +32 -0
- package/dist/scheduler/cron-parser.js +198 -0
- package/dist/scheduler/cron-parser.test.d.ts +1 -0
- package/dist/scheduler/cron-parser.test.js +188 -0
- package/dist/scheduler/index.d.ts +3 -0
- package/dist/scheduler/index.js +2 -0
- package/dist/scheduler/scheduler.d.ts +81 -0
- package/dist/scheduler/scheduler.js +376 -0
- package/dist/scheduler/types.d.ts +65 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/stores/factory.d.ts +36 -0
- package/dist/stores/factory.js +73 -0
- package/dist/stores/file.d.ts +60 -0
- package/dist/stores/file.js +173 -0
- package/dist/stores/file.test.d.ts +1 -0
- package/dist/stores/file.test.js +165 -0
- package/dist/stores/index.d.ts +6 -0
- package/dist/stores/index.js +5 -0
- package/dist/stores/memory.d.ts +19 -0
- package/dist/stores/memory.js +51 -0
- package/dist/stores/memory.test.d.ts +1 -0
- package/dist/stores/memory.test.js +157 -0
- package/dist/stores/postgrest.d.ts +55 -0
- package/dist/stores/postgrest.js +217 -0
- package/dist/stores/stores.test.d.ts +1 -0
- package/dist/stores/stores.test.js +158 -0
- package/dist/stores/types.d.ts +31 -0
- package/dist/stores/types.js +26 -0
- package/dist/sync/index.d.ts +4 -0
- package/dist/sync/index.js +2 -0
- package/dist/sync/state.d.ts +69 -0
- package/dist/sync/state.js +66 -0
- package/dist/sync/store.d.ts +49 -0
- package/dist/sync/store.js +93 -0
- package/dist/sync/sync.test.d.ts +1 -0
- package/dist/sync/sync.test.js +221 -0
- package/dist/utils/async.d.ts +7 -0
- package/dist/utils/async.js +9 -0
- package/dist/utils/file.d.ts +38 -0
- package/dist/utils/file.js +92 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/logger.d.ts +34 -0
- package/dist/utils/logger.js +39 -0
- package/dist/utils/path.d.ts +12 -0
- package/dist/utils/path.js +41 -0
- package/dist/webhook/index.d.ts +8 -0
- package/dist/webhook/index.js +7 -0
- package/dist/webhook/server.d.ts +84 -0
- package/dist/webhook/server.js +319 -0
- package/dist/webhook/store.d.ts +67 -0
- package/dist/webhook/store.js +193 -0
- package/dist/webhook/types.d.ts +88 -0
- package/dist/webhook/types.js +6 -0
- package/docusaurus/README.md +41 -0
- package/docusaurus/docs/advanced/execution-state.md +283 -0
- package/docusaurus/docs/advanced/extending-reqon.md +388 -0
- package/docusaurus/docs/advanced/multi-file-missions.md +250 -0
- package/docusaurus/docs/advanced/parallel-execution.md +353 -0
- package/docusaurus/docs/api-reference.md +443 -0
- package/docusaurus/docs/authentication/api-key.md +339 -0
- package/docusaurus/docs/authentication/basic.md +276 -0
- package/docusaurus/docs/authentication/bearer.md +282 -0
- package/docusaurus/docs/authentication/oauth2.md +317 -0
- package/docusaurus/docs/authentication/overview.md +251 -0
- package/docusaurus/docs/cli.md +229 -0
- package/docusaurus/docs/core-concepts/actions.md +286 -0
- package/docusaurus/docs/core-concepts/missions.md +264 -0
- package/docusaurus/docs/core-concepts/schemas.md +353 -0
- package/docusaurus/docs/core-concepts/sources.md +339 -0
- package/docusaurus/docs/core-concepts/stores.md +332 -0
- package/docusaurus/docs/dsl-syntax/expressions.md +361 -0
- package/docusaurus/docs/dsl-syntax/fetch.md +293 -0
- package/docusaurus/docs/dsl-syntax/for-loops.md +324 -0
- package/docusaurus/docs/dsl-syntax/map.md +345 -0
- package/docusaurus/docs/dsl-syntax/match.md +387 -0
- package/docusaurus/docs/dsl-syntax/pipelines.md +397 -0
- package/docusaurus/docs/dsl-syntax/validate.md +401 -0
- package/docusaurus/docs/error-handling/dead-letter-queues.md +399 -0
- package/docusaurus/docs/error-handling/flow-control.md +337 -0
- package/docusaurus/docs/error-handling/retry-strategies.md +368 -0
- package/docusaurus/docs/examples.md +488 -0
- package/docusaurus/docs/getting-started.md +256 -0
- package/docusaurus/docs/http/circuit-breaker.md +401 -0
- package/docusaurus/docs/http/incremental-sync.md +394 -0
- package/docusaurus/docs/http/pagination.md +361 -0
- package/docusaurus/docs/http/rate-limiting.md +383 -0
- package/docusaurus/docs/http/requests.md +328 -0
- package/docusaurus/docs/http/retry.md +402 -0
- package/docusaurus/docs/intro.md +90 -0
- package/docusaurus/docs/openapi/loading-specs.md +305 -0
- package/docusaurus/docs/openapi/operation-calls.md +314 -0
- package/docusaurus/docs/openapi/overview.md +212 -0
- package/docusaurus/docs/openapi/response-validation.md +344 -0
- package/docusaurus/docs/scheduling/cron.md +305 -0
- package/docusaurus/docs/scheduling/daemon-mode.md +317 -0
- package/docusaurus/docs/scheduling/intervals.md +289 -0
- package/docusaurus/docs/scheduling/overview.md +231 -0
- package/docusaurus/docs/stores/custom-adapters.md +376 -0
- package/docusaurus/docs/stores/file.md +236 -0
- package/docusaurus/docs/stores/memory.md +193 -0
- package/docusaurus/docs/stores/overview.md +274 -0
- package/docusaurus/docs/stores/postgrest.md +316 -0
- package/docusaurus/docusaurus.config.ts +148 -0
- package/docusaurus/package-lock.json +18029 -0
- package/docusaurus/package.json +47 -0
- package/docusaurus/sidebars.ts +155 -0
- package/docusaurus/src/components/HomepageFeatures/index.tsx +105 -0
- package/docusaurus/src/components/HomepageFeatures/styles.module.css +12 -0
- package/docusaurus/src/css/custom.css +169 -0
- package/docusaurus/src/pages/index.module.css +48 -0
- package/docusaurus/src/pages/index.tsx +110 -0
- package/docusaurus/src/pages/markdown-page.md +7 -0
- package/docusaurus/static/.nojekyll +0 -0
- package/docusaurus/static/img/docusaurus-social-card.jpg +0 -0
- package/docusaurus/static/img/docusaurus.png +0 -0
- package/docusaurus/static/img/favicon.ico +0 -0
- package/docusaurus/static/img/logo.svg +10 -0
- package/docusaurus/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docusaurus/static/img/undraw_docusaurus_react.svg +170 -0
- package/docusaurus/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docusaurus/tsconfig.json +8 -0
- package/examples/README.md +112 -0
- package/examples/error-handling/README.md +150 -0
- package/examples/error-handling/payment-processor.vague +287 -0
- package/examples/github-sync/README.md +74 -0
- package/examples/github-sync/fetch-issues.vague +47 -0
- package/examples/github-sync/fetch-prs.vague +40 -0
- package/examples/github-sync/mission.vague +101 -0
- package/examples/github-sync/normalize.vague +70 -0
- package/examples/jsonplaceholder/README.md +28 -0
- package/examples/jsonplaceholder/posts.vague +48 -0
- package/examples/petstore/README.md +35 -0
- package/examples/petstore/openapi.yaml +97 -0
- package/examples/petstore/sync.vague +52 -0
- package/examples/temporal-comparison/README.md +297 -0
- package/examples/temporal-comparison/reconciliation.vague +355 -0
- package/examples/temporal-comparison/temporal/activities/index.ts +8 -0
- package/examples/temporal-comparison/temporal/activities/shipstation.ts +225 -0
- package/examples/temporal-comparison/temporal/activities/shopify.ts +257 -0
- package/examples/temporal-comparison/temporal/activities/storage.ts +198 -0
- package/examples/temporal-comparison/temporal/activities/stripe.ts +169 -0
- package/examples/temporal-comparison/temporal/activities/validation.ts +205 -0
- package/examples/temporal-comparison/temporal/client/schedule.ts +218 -0
- package/examples/temporal-comparison/temporal/config/retry.ts +63 -0
- package/examples/temporal-comparison/temporal/types/index.ts +129 -0
- package/examples/temporal-comparison/temporal/workers/main.ts +130 -0
- package/examples/temporal-comparison/temporal/workflows/orderReconciliation.ts +262 -0
- package/examples/xero/README.md +88 -0
- package/examples/xero/invoices.vague +189 -0
- package/package.json +40 -0
- package/src/api-integration.test.ts +954 -0
- package/src/ast/index.ts +1 -0
- package/src/ast/nodes.ts +310 -0
- package/src/auth/auth.test.ts +326 -0
- package/src/auth/circuit-breaker.test.ts +390 -0
- package/src/auth/circuit-breaker.ts +379 -0
- package/src/auth/credentials.test.ts +273 -0
- package/src/auth/credentials.ts +246 -0
- package/src/auth/index.ts +40 -0
- package/src/auth/oauth2-provider.ts +177 -0
- package/src/auth/rate-limiter.ts +459 -0
- package/src/auth/token-store.ts +177 -0
- package/src/auth/types.ts +159 -0
- package/src/benchmark/e2e.bench.ts +288 -0
- package/src/benchmark/evaluator.bench.ts +331 -0
- package/src/benchmark/fixtures.ts +295 -0
- package/src/benchmark/index.ts +108 -0
- package/src/benchmark/lexer.bench.ts +69 -0
- package/src/benchmark/parser.bench.ts +103 -0
- package/src/benchmark/resilience.bench.ts +193 -0
- package/src/benchmark/store.bench.ts +147 -0
- package/src/benchmark/utils.ts +230 -0
- package/src/cli.ts +313 -0
- package/src/errors/errors.test.ts +234 -0
- package/src/errors/index.ts +223 -0
- package/src/execution/execution.test.ts +307 -0
- package/src/execution/index.ts +21 -0
- package/src/execution/state.ts +207 -0
- package/src/execution/store.ts +188 -0
- package/src/index.ts +169 -0
- package/src/integration.test.ts +192 -0
- package/src/interpreter/context.ts +57 -0
- package/src/interpreter/evaluator.test.ts +796 -0
- package/src/interpreter/evaluator.ts +245 -0
- package/src/interpreter/executor.ts +946 -0
- package/src/interpreter/fetch-handler.ts +302 -0
- package/src/interpreter/http.test.ts +423 -0
- package/src/interpreter/http.ts +308 -0
- package/src/interpreter/index.ts +32 -0
- package/src/interpreter/pagination.ts +207 -0
- package/src/interpreter/progress.test.ts +276 -0
- package/src/interpreter/schema-matcher.test.ts +160 -0
- package/src/interpreter/schema-matcher.ts +168 -0
- package/src/interpreter/signals.ts +73 -0
- package/src/interpreter/step-handlers/for-handler.ts +65 -0
- package/src/interpreter/step-handlers/index.ts +17 -0
- package/src/interpreter/step-handlers/map-handler.ts +24 -0
- package/src/interpreter/step-handlers/match-handler.ts +101 -0
- package/src/interpreter/step-handlers/store-handler.ts +78 -0
- package/src/interpreter/step-handlers/types.ts +17 -0
- package/src/interpreter/step-handlers/validate-handler.ts +30 -0
- package/src/interpreter/step-handlers/webhook-handler.ts +142 -0
- package/src/lexer/index.ts +18 -0
- package/src/lexer/lexer.test.ts +316 -0
- package/src/lexer/tokens.ts +179 -0
- package/src/loader/index.ts +288 -0
- package/src/loader/loader.test.ts +360 -0
- package/src/oas/index.ts +4 -0
- package/src/oas/loader.ts +126 -0
- package/src/oas/oas.test.ts +254 -0
- package/src/oas/validator.ts +299 -0
- package/src/parser/base.ts +124 -0
- package/src/parser/expressions.test.ts +525 -0
- package/src/parser/expressions.ts +314 -0
- package/src/parser/index.ts +3 -0
- package/src/parser/match.test.ts +296 -0
- package/src/parser/parser.test.ts +739 -0
- package/src/parser/parser.ts +1469 -0
- package/src/parser/schedule.test.ts +287 -0
- package/src/parser/webhook.test.ts +248 -0
- package/src/plugin.ts +83 -0
- package/src/scheduler/cron-parser.test.ts +236 -0
- package/src/scheduler/cron-parser.ts +236 -0
- package/src/scheduler/index.ts +10 -0
- package/src/scheduler/scheduler.ts +443 -0
- package/src/scheduler/types.ts +71 -0
- package/src/stores/factory.ts +104 -0
- package/src/stores/file.test.ts +276 -0
- package/src/stores/file.ts +211 -0
- package/src/stores/index.ts +6 -0
- package/src/stores/memory.test.ts +238 -0
- package/src/stores/memory.ts +63 -0
- package/src/stores/postgrest.test.ts +488 -0
- package/src/stores/postgrest.ts +263 -0
- package/src/stores/stores.test.ts +197 -0
- package/src/stores/types.ts +58 -0
- package/src/sync/index.ts +16 -0
- package/src/sync/state.ts +126 -0
- package/src/sync/store.ts +139 -0
- package/src/sync/sync.test.ts +271 -0
- package/src/utils/async.ts +10 -0
- package/src/utils/file.ts +106 -0
- package/src/utils/index.ts +14 -0
- package/src/utils/logger.ts +53 -0
- package/src/utils/path.ts +47 -0
- package/src/webhook/index.ts +15 -0
- package/src/webhook/server.test.ts +253 -0
- package/src/webhook/server.ts +389 -0
- package/src/webhook/store.ts +239 -0
- package/src/webhook/types.ts +93 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +39 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { loadOAS, resolveOperation, getResponseSchema, clearCache } from './loader.js';
|
|
2
|
+
export type { OASSource, OASOperation, OpenAPISpec } from './loader.js';
|
|
3
|
+
export { validateResponse } from './validator.js';
|
|
4
|
+
export type { ValidationResult, ValidationError } from './validator.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
|
|
2
|
+
export type OpenAPISpec = OpenAPIV3.Document | OpenAPIV3_1.Document;
|
|
3
|
+
export interface OASOperation {
|
|
4
|
+
operationId: string;
|
|
5
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
6
|
+
path: string;
|
|
7
|
+
parameters?: OpenAPIV3.ParameterObject[];
|
|
8
|
+
requestBody?: OpenAPIV3.RequestBodyObject;
|
|
9
|
+
responses?: OpenAPIV3.ResponsesObject;
|
|
10
|
+
security?: OpenAPIV3.SecurityRequirementObject[];
|
|
11
|
+
}
|
|
12
|
+
export interface OASSource {
|
|
13
|
+
spec: OpenAPISpec;
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
operations: Map<string, OASOperation>;
|
|
16
|
+
schemas: Map<string, OpenAPIV3.SchemaObject>;
|
|
17
|
+
}
|
|
18
|
+
export declare function loadOAS(specPath: string, forceReload?: boolean): Promise<OASSource>;
|
|
19
|
+
export declare function resolveOperation(source: OASSource, operationId: string): OASOperation;
|
|
20
|
+
export declare function getResponseSchema(source: OASSource, operationId: string, statusCode?: string): OpenAPIV3.SchemaObject | undefined;
|
|
21
|
+
export declare function clearCache(): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import SwaggerParser from '@apidevtools/swagger-parser';
|
|
2
|
+
// Cache loaded specs to avoid re-parsing
|
|
3
|
+
const specCache = new Map();
|
|
4
|
+
export async function loadOAS(specPath, forceReload = false) {
|
|
5
|
+
if (!forceReload && specCache.has(specPath)) {
|
|
6
|
+
return specCache.get(specPath);
|
|
7
|
+
}
|
|
8
|
+
const api = await SwaggerParser.dereference(specPath);
|
|
9
|
+
const baseUrl = extractBaseUrl(api);
|
|
10
|
+
const operations = extractOperations(api);
|
|
11
|
+
const schemas = extractSchemas(api);
|
|
12
|
+
const source = {
|
|
13
|
+
spec: api,
|
|
14
|
+
baseUrl,
|
|
15
|
+
operations,
|
|
16
|
+
schemas,
|
|
17
|
+
};
|
|
18
|
+
specCache.set(specPath, source);
|
|
19
|
+
return source;
|
|
20
|
+
}
|
|
21
|
+
function extractBaseUrl(spec) {
|
|
22
|
+
if (spec.servers && spec.servers.length > 0) {
|
|
23
|
+
return spec.servers[0].url;
|
|
24
|
+
}
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
function extractOperations(spec) {
|
|
28
|
+
const operations = new Map();
|
|
29
|
+
if (!spec.paths)
|
|
30
|
+
return operations;
|
|
31
|
+
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
32
|
+
if (!pathItem)
|
|
33
|
+
continue;
|
|
34
|
+
const methods = ['get', 'post', 'put', 'patch', 'delete'];
|
|
35
|
+
for (const method of methods) {
|
|
36
|
+
const operation = pathItem[method];
|
|
37
|
+
if (!operation?.operationId)
|
|
38
|
+
continue;
|
|
39
|
+
operations.set(operation.operationId, {
|
|
40
|
+
operationId: operation.operationId,
|
|
41
|
+
method: method.toUpperCase(),
|
|
42
|
+
path,
|
|
43
|
+
parameters: operation.parameters,
|
|
44
|
+
requestBody: operation.requestBody,
|
|
45
|
+
responses: operation.responses,
|
|
46
|
+
security: operation.security,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return operations;
|
|
51
|
+
}
|
|
52
|
+
function extractSchemas(spec) {
|
|
53
|
+
const schemas = new Map();
|
|
54
|
+
const components = spec.components;
|
|
55
|
+
if (!components?.schemas)
|
|
56
|
+
return schemas;
|
|
57
|
+
for (const [name, schema] of Object.entries(components.schemas)) {
|
|
58
|
+
schemas.set(name, schema);
|
|
59
|
+
}
|
|
60
|
+
return schemas;
|
|
61
|
+
}
|
|
62
|
+
export function resolveOperation(source, operationId) {
|
|
63
|
+
const operation = source.operations.get(operationId);
|
|
64
|
+
if (!operation) {
|
|
65
|
+
const available = Array.from(source.operations.keys()).slice(0, 5).join(', ');
|
|
66
|
+
throw new Error(`Operation '${operationId}' not found in OAS spec. Available: ${available}...`);
|
|
67
|
+
}
|
|
68
|
+
return operation;
|
|
69
|
+
}
|
|
70
|
+
export function getResponseSchema(source, operationId, statusCode = '200') {
|
|
71
|
+
const operation = resolveOperation(source, operationId);
|
|
72
|
+
const response = operation.responses?.[statusCode];
|
|
73
|
+
if (!response?.content)
|
|
74
|
+
return undefined;
|
|
75
|
+
const jsonContent = response.content['application/json'];
|
|
76
|
+
if (!jsonContent?.schema)
|
|
77
|
+
return undefined;
|
|
78
|
+
return jsonContent.schema;
|
|
79
|
+
}
|
|
80
|
+
export function clearCache() {
|
|
81
|
+
specCache.clear();
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import { loadOAS, resolveOperation, getResponseSchema, clearCache } from './loader.js';
|
|
3
|
+
import { validateResponse } from './validator.js';
|
|
4
|
+
import { ReqonLexer } from '../lexer/index.js';
|
|
5
|
+
import { ReqonParser } from '../parser/parser.js';
|
|
6
|
+
describe('OAS Loader', () => {
|
|
7
|
+
beforeAll(() => {
|
|
8
|
+
clearCache();
|
|
9
|
+
});
|
|
10
|
+
it('loads and parses an OpenAPI spec', async () => {
|
|
11
|
+
const source = await loadOAS('./examples/petstore/openapi.yaml');
|
|
12
|
+
expect(source.baseUrl).toBe('https://api.petstore.example.com/v1');
|
|
13
|
+
expect(source.operations.size).toBe(3);
|
|
14
|
+
expect(source.schemas.size).toBe(2);
|
|
15
|
+
});
|
|
16
|
+
it('resolves operations by operationId', async () => {
|
|
17
|
+
const source = await loadOAS('./examples/petstore/openapi.yaml');
|
|
18
|
+
const listPets = resolveOperation(source, 'listPets');
|
|
19
|
+
expect(listPets.method).toBe('GET');
|
|
20
|
+
expect(listPets.path).toBe('/pets');
|
|
21
|
+
const getPet = resolveOperation(source, 'getPet');
|
|
22
|
+
expect(getPet.method).toBe('GET');
|
|
23
|
+
expect(getPet.path).toBe('/pets/{petId}');
|
|
24
|
+
const createPet = resolveOperation(source, 'createPet');
|
|
25
|
+
expect(createPet.method).toBe('POST');
|
|
26
|
+
expect(createPet.path).toBe('/pets');
|
|
27
|
+
});
|
|
28
|
+
it('throws for unknown operationId', async () => {
|
|
29
|
+
const source = await loadOAS('./examples/petstore/openapi.yaml');
|
|
30
|
+
expect(() => resolveOperation(source, 'unknownOp')).toThrow(/not found/);
|
|
31
|
+
});
|
|
32
|
+
it('extracts response schemas', async () => {
|
|
33
|
+
const source = await loadOAS('./examples/petstore/openapi.yaml');
|
|
34
|
+
const schema = getResponseSchema(source, 'getPet');
|
|
35
|
+
expect(schema).toBeDefined();
|
|
36
|
+
expect(schema?.type).toBe('object');
|
|
37
|
+
expect(schema?.properties).toHaveProperty('id');
|
|
38
|
+
expect(schema?.properties).toHaveProperty('name');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('OAS Validator', () => {
|
|
42
|
+
it('validates a valid response', () => {
|
|
43
|
+
const schema = {
|
|
44
|
+
type: 'object',
|
|
45
|
+
required: ['id', 'name'],
|
|
46
|
+
properties: {
|
|
47
|
+
id: { type: 'string' },
|
|
48
|
+
name: { type: 'string' },
|
|
49
|
+
age: { type: 'integer', minimum: 0 },
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const data = { id: '123', name: 'Fluffy', age: 3 };
|
|
53
|
+
const result = validateResponse(data, schema);
|
|
54
|
+
expect(result.valid).toBe(true);
|
|
55
|
+
expect(result.errors).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
it('catches missing required properties', () => {
|
|
58
|
+
const schema = {
|
|
59
|
+
type: 'object',
|
|
60
|
+
required: ['id', 'name'],
|
|
61
|
+
properties: {
|
|
62
|
+
id: { type: 'string' },
|
|
63
|
+
name: { type: 'string' },
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const data = { id: '123' }; // missing 'name'
|
|
67
|
+
const result = validateResponse(data, schema);
|
|
68
|
+
expect(result.valid).toBe(false);
|
|
69
|
+
expect(result.errors.some(e => e.path === 'name')).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it('catches type mismatches', () => {
|
|
72
|
+
const schema = {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
age: { type: 'integer' },
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const data = { age: 'not a number' };
|
|
79
|
+
const result = validateResponse(data, schema);
|
|
80
|
+
expect(result.valid).toBe(false);
|
|
81
|
+
expect(result.errors[0].path).toBe('age');
|
|
82
|
+
});
|
|
83
|
+
it('validates arrays', () => {
|
|
84
|
+
const schema = {
|
|
85
|
+
type: 'array',
|
|
86
|
+
items: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
required: ['id'],
|
|
89
|
+
properties: {
|
|
90
|
+
id: { type: 'string' },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
const validData = [{ id: '1' }, { id: '2' }];
|
|
95
|
+
expect(validateResponse(validData, schema).valid).toBe(true);
|
|
96
|
+
const invalidData = [{ id: '1' }, { noId: true }];
|
|
97
|
+
const result = validateResponse(invalidData, schema);
|
|
98
|
+
expect(result.valid).toBe(false);
|
|
99
|
+
expect(result.errors[0].path).toBe('[1].id');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe('OAS Parser Integration', () => {
|
|
103
|
+
function parse(source) {
|
|
104
|
+
const lexer = new ReqonLexer(source);
|
|
105
|
+
const tokens = lexer.tokenize();
|
|
106
|
+
const parser = new ReqonParser(tokens);
|
|
107
|
+
return parser.parse();
|
|
108
|
+
}
|
|
109
|
+
it('parses source with OAS spec path', () => {
|
|
110
|
+
const source = `
|
|
111
|
+
mission TestOAS {
|
|
112
|
+
source Petstore from "./examples/petstore.yaml" {
|
|
113
|
+
auth: bearer
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
store pets: memory("pets")
|
|
117
|
+
|
|
118
|
+
action FetchPets {
|
|
119
|
+
fetch Petstore.listPets
|
|
120
|
+
|
|
121
|
+
store response.pets -> pets {
|
|
122
|
+
key: .id
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
run FetchPets
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
const program = parse(source);
|
|
130
|
+
expect(program.type).toBe('ReqonProgram');
|
|
131
|
+
const mission = program.statements[0];
|
|
132
|
+
if (mission.type === 'MissionDefinition') {
|
|
133
|
+
expect(mission.sources[0].specPath).toBe('./examples/petstore.yaml');
|
|
134
|
+
expect(mission.sources[0].config.base).toBeUndefined();
|
|
135
|
+
const action = mission.actions[0];
|
|
136
|
+
const fetchStep = action.steps[0];
|
|
137
|
+
if (fetchStep.type === 'FetchStep') {
|
|
138
|
+
expect(fetchStep.operationRef).toBeDefined();
|
|
139
|
+
expect(fetchStep.operationRef?.source).toBe('Petstore');
|
|
140
|
+
expect(fetchStep.operationRef?.operationId).toBe('listPets');
|
|
141
|
+
expect(fetchStep.method).toBeUndefined();
|
|
142
|
+
expect(fetchStep.path).toBeUndefined();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
it('parses source with OAS and explicit base URL override', () => {
|
|
147
|
+
const source = `
|
|
148
|
+
mission TestOAS {
|
|
149
|
+
source Petstore from "./examples/petstore.yaml" {
|
|
150
|
+
auth: bearer,
|
|
151
|
+
base: "https://staging.petstore.com/v1"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
store pets: memory("pets")
|
|
155
|
+
|
|
156
|
+
action Fetch {
|
|
157
|
+
fetch GET "/custom-path"
|
|
158
|
+
store response -> pets { key: .id }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
run Fetch
|
|
162
|
+
}
|
|
163
|
+
`;
|
|
164
|
+
const program = parse(source);
|
|
165
|
+
const mission = program.statements[0];
|
|
166
|
+
if (mission.type === 'MissionDefinition') {
|
|
167
|
+
expect(mission.sources[0].specPath).toBe('./examples/petstore.yaml');
|
|
168
|
+
expect(mission.sources[0].config.base).toBe('https://staging.petstore.com/v1');
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
it('supports both OAS and traditional fetch in same mission', () => {
|
|
172
|
+
const source = `
|
|
173
|
+
mission MixedFetch {
|
|
174
|
+
source Petstore from "./examples/petstore.yaml" {
|
|
175
|
+
auth: bearer
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
source Legacy {
|
|
179
|
+
auth: bearer,
|
|
180
|
+
base: "https://legacy.api.com"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
store pets: memory("pets")
|
|
184
|
+
|
|
185
|
+
action FetchFromOAS {
|
|
186
|
+
fetch Petstore.listPets
|
|
187
|
+
store response.pets -> pets { key: .id }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
action FetchFromLegacy {
|
|
191
|
+
fetch GET "/old-endpoint" {
|
|
192
|
+
source: Legacy
|
|
193
|
+
}
|
|
194
|
+
store response -> pets { key: .id }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
run FetchFromOAS then FetchFromLegacy
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
const program = parse(source);
|
|
201
|
+
const mission = program.statements[0];
|
|
202
|
+
if (mission.type === 'MissionDefinition') {
|
|
203
|
+
expect(mission.sources).toHaveLength(2);
|
|
204
|
+
const oasAction = mission.actions[0];
|
|
205
|
+
const legacyAction = mission.actions[1];
|
|
206
|
+
const oasFetch = oasAction.steps[0];
|
|
207
|
+
const legacyFetch = legacyAction.steps[0];
|
|
208
|
+
if (oasFetch.type === 'FetchStep' && legacyFetch.type === 'FetchStep') {
|
|
209
|
+
// OAS fetch
|
|
210
|
+
expect(oasFetch.operationRef).toBeDefined();
|
|
211
|
+
expect(oasFetch.method).toBeUndefined();
|
|
212
|
+
// Traditional fetch
|
|
213
|
+
expect(legacyFetch.operationRef).toBeUndefined();
|
|
214
|
+
expect(legacyFetch.method).toBe('GET');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
export interface ValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: ValidationError[];
|
|
5
|
+
}
|
|
6
|
+
export interface ValidationError {
|
|
7
|
+
path: string;
|
|
8
|
+
message: string;
|
|
9
|
+
expected?: string;
|
|
10
|
+
actual?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function validateResponse(data: unknown, schema: OpenAPIV3.SchemaObject, path?: string): ValidationResult;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
export function validateResponse(data, schema, path = '') {
|
|
2
|
+
const errors = [];
|
|
3
|
+
validateValue(data, schema, path, errors);
|
|
4
|
+
return {
|
|
5
|
+
valid: errors.length === 0,
|
|
6
|
+
errors,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function validateValue(value, schema, path, errors) {
|
|
10
|
+
// Handle nullable
|
|
11
|
+
if (value === null) {
|
|
12
|
+
if (schema.nullable)
|
|
13
|
+
return;
|
|
14
|
+
errors.push({ path, message: 'Value is null but schema is not nullable' });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Handle undefined/missing
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
// Required check happens at object level
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Check type
|
|
23
|
+
const schemaType = schema.type;
|
|
24
|
+
switch (schemaType) {
|
|
25
|
+
case 'string':
|
|
26
|
+
validateString(value, schema, path, errors);
|
|
27
|
+
break;
|
|
28
|
+
case 'number':
|
|
29
|
+
case 'integer':
|
|
30
|
+
validateNumber(value, schema, path, errors);
|
|
31
|
+
break;
|
|
32
|
+
case 'boolean':
|
|
33
|
+
validateBoolean(value, path, errors);
|
|
34
|
+
break;
|
|
35
|
+
case 'array':
|
|
36
|
+
validateArray(value, schema, path, errors);
|
|
37
|
+
break;
|
|
38
|
+
case 'object':
|
|
39
|
+
validateObject(value, schema, path, errors);
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
// No type specified, or unknown type - allow anything
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
// Check enum
|
|
46
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
47
|
+
errors.push({
|
|
48
|
+
path,
|
|
49
|
+
message: `Value not in enum`,
|
|
50
|
+
expected: schema.enum.join(' | '),
|
|
51
|
+
actual: String(value),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function validateString(value, schema, path, errors) {
|
|
56
|
+
if (typeof value !== 'string') {
|
|
57
|
+
errors.push({
|
|
58
|
+
path,
|
|
59
|
+
message: 'Expected string',
|
|
60
|
+
expected: 'string',
|
|
61
|
+
actual: typeof value,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
66
|
+
errors.push({
|
|
67
|
+
path,
|
|
68
|
+
message: `String too short`,
|
|
69
|
+
expected: `>= ${schema.minLength} chars`,
|
|
70
|
+
actual: `${value.length} chars`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
|
74
|
+
errors.push({
|
|
75
|
+
path,
|
|
76
|
+
message: `String too long`,
|
|
77
|
+
expected: `<= ${schema.maxLength} chars`,
|
|
78
|
+
actual: `${value.length} chars`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
if (schema.pattern) {
|
|
82
|
+
const regex = new RegExp(schema.pattern);
|
|
83
|
+
if (!regex.test(value)) {
|
|
84
|
+
errors.push({
|
|
85
|
+
path,
|
|
86
|
+
message: `String does not match pattern`,
|
|
87
|
+
expected: schema.pattern,
|
|
88
|
+
actual: value,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function validateNumber(value, schema, path, errors) {
|
|
94
|
+
if (typeof value !== 'number') {
|
|
95
|
+
errors.push({
|
|
96
|
+
path,
|
|
97
|
+
message: 'Expected number',
|
|
98
|
+
expected: schema.type ?? 'number',
|
|
99
|
+
actual: typeof value,
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (schema.type === 'integer' && !Number.isInteger(value)) {
|
|
104
|
+
errors.push({
|
|
105
|
+
path,
|
|
106
|
+
message: 'Expected integer',
|
|
107
|
+
expected: 'integer',
|
|
108
|
+
actual: String(value),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
112
|
+
errors.push({
|
|
113
|
+
path,
|
|
114
|
+
message: `Number below minimum`,
|
|
115
|
+
expected: `>= ${schema.minimum}`,
|
|
116
|
+
actual: String(value),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (schema.maximum !== undefined && value > schema.maximum) {
|
|
120
|
+
errors.push({
|
|
121
|
+
path,
|
|
122
|
+
message: `Number above maximum`,
|
|
123
|
+
expected: `<= ${schema.maximum}`,
|
|
124
|
+
actual: String(value),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function validateBoolean(value, path, errors) {
|
|
129
|
+
if (typeof value !== 'boolean') {
|
|
130
|
+
errors.push({
|
|
131
|
+
path,
|
|
132
|
+
message: 'Expected boolean',
|
|
133
|
+
expected: 'boolean',
|
|
134
|
+
actual: typeof value,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function validateArray(value, schema, path, errors) {
|
|
139
|
+
if (!Array.isArray(value)) {
|
|
140
|
+
errors.push({
|
|
141
|
+
path,
|
|
142
|
+
message: 'Expected array',
|
|
143
|
+
expected: 'array',
|
|
144
|
+
actual: typeof value,
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (schema.minItems !== undefined && value.length < schema.minItems) {
|
|
149
|
+
errors.push({
|
|
150
|
+
path,
|
|
151
|
+
message: `Array too short`,
|
|
152
|
+
expected: `>= ${schema.minItems} items`,
|
|
153
|
+
actual: `${value.length} items`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (schema.maxItems !== undefined && value.length > schema.maxItems) {
|
|
157
|
+
errors.push({
|
|
158
|
+
path,
|
|
159
|
+
message: `Array too long`,
|
|
160
|
+
expected: `<= ${schema.maxItems} items`,
|
|
161
|
+
actual: `${value.length} items`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// Validate items
|
|
165
|
+
const arraySchema = schema;
|
|
166
|
+
if (arraySchema.items) {
|
|
167
|
+
const itemSchema = arraySchema.items;
|
|
168
|
+
value.forEach((item, index) => {
|
|
169
|
+
validateValue(item, itemSchema, `${path}[${index}]`, errors);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function validateObject(value, schema, path, errors) {
|
|
174
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
175
|
+
errors.push({
|
|
176
|
+
path,
|
|
177
|
+
message: 'Expected object',
|
|
178
|
+
expected: 'object',
|
|
179
|
+
actual: Array.isArray(value) ? 'array' : typeof value,
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const obj = value;
|
|
184
|
+
// Check required properties
|
|
185
|
+
if (schema.required) {
|
|
186
|
+
for (const prop of schema.required) {
|
|
187
|
+
if (!(prop in obj)) {
|
|
188
|
+
errors.push({
|
|
189
|
+
path: path ? `${path}.${prop}` : prop,
|
|
190
|
+
message: `Missing required property`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Validate properties
|
|
196
|
+
const properties = schema.properties;
|
|
197
|
+
if (properties) {
|
|
198
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
199
|
+
if (key in obj) {
|
|
200
|
+
const propPath = path ? `${path}.${key}` : key;
|
|
201
|
+
validateValue(obj[key], propSchema, propPath, errors);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Handle additionalProperties
|
|
206
|
+
if (schema.additionalProperties === false) {
|
|
207
|
+
const allowedKeys = new Set(Object.keys(properties ?? {}));
|
|
208
|
+
for (const key of Object.keys(obj)) {
|
|
209
|
+
if (!allowedKeys.has(key)) {
|
|
210
|
+
errors.push({
|
|
211
|
+
path: path ? `${path}.${key}` : key,
|
|
212
|
+
message: `Unexpected property`,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (typeof schema.additionalProperties === 'object') {
|
|
218
|
+
const additionalSchema = schema.additionalProperties;
|
|
219
|
+
const allowedKeys = new Set(Object.keys(properties ?? {}));
|
|
220
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
221
|
+
if (!allowedKeys.has(key)) {
|
|
222
|
+
const propPath = path ? `${path}.${key}` : key;
|
|
223
|
+
validateValue(val, additionalSchema, propPath, errors);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TokenType, type Token } from 'vague-lang';
|
|
2
|
+
import { ReqonTokenType } from '../lexer/tokens.js';
|
|
3
|
+
import { ParseError } from '../errors/index.js';
|
|
4
|
+
type AnyTokenType = TokenType | ReqonTokenType | string;
|
|
5
|
+
export declare class ReqonParserBase {
|
|
6
|
+
protected tokens: Token[];
|
|
7
|
+
protected pos: number;
|
|
8
|
+
protected source?: string;
|
|
9
|
+
protected filePath?: string;
|
|
10
|
+
constructor(tokens: Token[], source?: string, filePath?: string);
|
|
11
|
+
protected peek(): Token;
|
|
12
|
+
protected peekNext(): Token | undefined;
|
|
13
|
+
protected check(type: AnyTokenType): boolean;
|
|
14
|
+
protected checkAny(...types: AnyTokenType[]): boolean;
|
|
15
|
+
protected match(type: AnyTokenType): boolean;
|
|
16
|
+
protected matchAny(...types: AnyTokenType[]): AnyTokenType | null;
|
|
17
|
+
protected advance(): Token;
|
|
18
|
+
protected consume(type: AnyTokenType, message: string): Token;
|
|
19
|
+
/**
|
|
20
|
+
* Consume an identifier, allowing HTTP method tokens to be used as identifiers.
|
|
21
|
+
* This is needed because 'get', 'post', etc. are valid variable/store names.
|
|
22
|
+
*/
|
|
23
|
+
protected consumeIdentifier(message: string): Token;
|
|
24
|
+
/**
|
|
25
|
+
* Check if current token is an identifier (including HTTP methods as identifiers)
|
|
26
|
+
*/
|
|
27
|
+
protected checkIdentifier(): boolean;
|
|
28
|
+
protected isAtEnd(): boolean;
|
|
29
|
+
protected error(message: string): ParseError;
|
|
30
|
+
protected savePosition(): number;
|
|
31
|
+
protected restorePosition(saved: number): void;
|
|
32
|
+
}
|
|
33
|
+
export {};
|