From f1ce803c93dfbaffa9bb2a172e777eedd69fc2ed Mon Sep 17 00:00:00 2001 From: Steve Konves Date: Sat, 23 Aug 2025 10:56:23 -0700 Subject: [PATCH 1/3] Generate on pre-build --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d6c6b0f..3238c59 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "fix": "run-s -s fix:*", "clean:output": "rimraf lib", "start": "node ./lib/index.js", - "prebuild": "run-s -s clean lint", + "prebuild": "basketry && run-s -s clean lint", "build": "tsc", "postbuild": "node -e \"require('fs').copyFileSync('src/schema.json','lib/schema.json')\"", "lint:eslint": "eslint src/**/*.*", From 8e70ac21850e47556addb96ebe53d63fe28c65dd Mon Sep 17 00:00:00 2001 From: Steve Konves Date: Sat, 23 Aug 2025 10:56:37 -0700 Subject: [PATCH 2/3] Upgrade to latest packages --- package-lock.json | 66 +++++++++++++++++++++++------------------------ package.json | 6 ++--- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae9cc2a..3c4dc11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,13 +12,13 @@ "ajv": "^8.17.1" }, "devDependencies": { - "@basketry/json-schema": "^0.2.0-rc.0", - "@basketry/typescript": "^0.2.0-rc.0", + "@basketry/json-schema": "^0.2.0", + "@basketry/typescript": "^0.2.2", "@types/jest": "^27.4.0", "@types/node": "^17.0.10", "@typescript-eslint/eslint-plugin": "^5.8.1", "@typescript-eslint/parser": "^5.8.1", - "basketry": "^0.2.0-rc.1", + "basketry": "^0.2.0", "eslint": "^8.5.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.3", @@ -508,9 +508,9 @@ } }, "node_modules/@basketry/ir": { - "version": "0.2.0-rc.0", - "resolved": "https://registry.npmjs.org/@basketry/ir/-/ir-0.2.0-rc.0.tgz", - "integrity": "sha512-MqanZpGhs3KflBmKU5mLSlm0p5wfVrLi02zXQ8qsMc72Xmxf17cU9q8xVZ4rmnuVEG4d6zKio6BZTSJDDu32GQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@basketry/ir/-/ir-0.2.0.tgz", + "integrity": "sha512-jHUGjuj6DlLMPn657PN/+jvzytBU8DBvQ5hJ24Qn2eM0JEUXc6hXAgyowXns8a3/q0AFwNNUwOCiYSULY8SDuw==", "dev": true, "dependencies": { "ajv": "^8.17.1" @@ -520,12 +520,12 @@ } }, "node_modules/@basketry/json-schema": { - "version": "0.2.0-rc.0", - "resolved": "https://registry.npmjs.org/@basketry/json-schema/-/json-schema-0.2.0-rc.0.tgz", - "integrity": "sha512-sDTntwKWr4bdTx/v3l3hmosOVhvqOVXmmSEdKUHFYT5wTF8VSFgMENQy6rsEqajpHW7U8V2usY2BL7lkrgxkvw==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@basketry/json-schema/-/json-schema-0.2.0.tgz", + "integrity": "sha512-MZ6M5rabmKhrF+FowUbVFiBUehtkH5rU7D4TioEUSRe/vytVPslFtFtYotqBMWjPBwmTIvNdiuTzTxOycWOHNQ==", "dev": true, "dependencies": { - "basketry": "^0.2.0-rc.1", + "basketry": "^0.2.0", "case": "^1.6.3", "json-to-ast": "^2.1.0", "pluralize": "^8.0.0", @@ -533,12 +533,12 @@ } }, "node_modules/@basketry/typescript": { - "version": "0.2.0-rc.0", - "resolved": "https://registry.npmjs.org/@basketry/typescript/-/typescript-0.2.0-rc.0.tgz", - "integrity": "sha512-BzseH0zXwyWGdgtF4E3/UVabJwZrFWegetMYGZKEgI0kie2Xru06/vs8q6EVcdF+MRfi1E4cVkgDP8m8bqvkpQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@basketry/typescript/-/typescript-0.2.2.tgz", + "integrity": "sha512-V/Gyr9c68NIvssfayFN2N+u/76gBSxohSaoXZuKWIhYm1r1fKW/4gjyjzqD3DrRgITRzo3dihxH0ASijv7Rjkg==", "dev": true, "dependencies": { - "basketry": "^0.2.0-rc.1", + "basketry": "^0.2.0", "case": "^1.6.3" }, "funding": { @@ -2153,12 +2153,12 @@ "dev": true }, "node_modules/basketry": { - "version": "0.2.0-rc.1", - "resolved": "https://registry.npmjs.org/basketry/-/basketry-0.2.0-rc.1.tgz", - "integrity": "sha512-kgcN6dYk32CwDnbEyyRt9vVBkDtJFxzmn7hzqYdTV0RUIy9oBsZtRtleAW00IRSIBDfUu8ffLUIZZE2q0nGJUQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/basketry/-/basketry-0.2.0.tgz", + "integrity": "sha512-LLd6QdP/2Mnhm2YGDnW5m95/bYaAJJYuuNeDLL/V2ohptorfFJcpnJFrMrfXWMGCnoQDd4ICPTiayKLoXqCkUw==", "dev": true, "dependencies": { - "@basketry/ir": "^0.2.0-rc.0", + "@basketry/ir": "^0.2.0", "case": "^1.6.3", "chalk": "^4.1.2", "ts-node": "^10.7.0", @@ -8674,21 +8674,21 @@ } }, "@basketry/ir": { - "version": "0.2.0-rc.0", - "resolved": "https://registry.npmjs.org/@basketry/ir/-/ir-0.2.0-rc.0.tgz", - "integrity": "sha512-MqanZpGhs3KflBmKU5mLSlm0p5wfVrLi02zXQ8qsMc72Xmxf17cU9q8xVZ4rmnuVEG4d6zKio6BZTSJDDu32GQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@basketry/ir/-/ir-0.2.0.tgz", + "integrity": "sha512-jHUGjuj6DlLMPn657PN/+jvzytBU8DBvQ5hJ24Qn2eM0JEUXc6hXAgyowXns8a3/q0AFwNNUwOCiYSULY8SDuw==", "dev": true, "requires": { "ajv": "^8.17.1" } }, "@basketry/json-schema": { - "version": "0.2.0-rc.0", - "resolved": "https://registry.npmjs.org/@basketry/json-schema/-/json-schema-0.2.0-rc.0.tgz", - "integrity": "sha512-sDTntwKWr4bdTx/v3l3hmosOVhvqOVXmmSEdKUHFYT5wTF8VSFgMENQy6rsEqajpHW7U8V2usY2BL7lkrgxkvw==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@basketry/json-schema/-/json-schema-0.2.0.tgz", + "integrity": "sha512-MZ6M5rabmKhrF+FowUbVFiBUehtkH5rU7D4TioEUSRe/vytVPslFtFtYotqBMWjPBwmTIvNdiuTzTxOycWOHNQ==", "dev": true, "requires": { - "basketry": "^0.2.0-rc.1", + "basketry": "^0.2.0", "case": "^1.6.3", "json-to-ast": "^2.1.0", "pluralize": "^8.0.0", @@ -8696,12 +8696,12 @@ } }, "@basketry/typescript": { - "version": "0.2.0-rc.0", - "resolved": "https://registry.npmjs.org/@basketry/typescript/-/typescript-0.2.0-rc.0.tgz", - "integrity": "sha512-BzseH0zXwyWGdgtF4E3/UVabJwZrFWegetMYGZKEgI0kie2Xru06/vs8q6EVcdF+MRfi1E4cVkgDP8m8bqvkpQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@basketry/typescript/-/typescript-0.2.2.tgz", + "integrity": "sha512-V/Gyr9c68NIvssfayFN2N+u/76gBSxohSaoXZuKWIhYm1r1fKW/4gjyjzqD3DrRgITRzo3dihxH0ASijv7Rjkg==", "dev": true, "requires": { - "basketry": "^0.2.0-rc.1", + "basketry": "^0.2.0", "case": "^1.6.3" } }, @@ -9933,12 +9933,12 @@ "dev": true }, "basketry": { - "version": "0.2.0-rc.1", - "resolved": "https://registry.npmjs.org/basketry/-/basketry-0.2.0-rc.1.tgz", - "integrity": "sha512-kgcN6dYk32CwDnbEyyRt9vVBkDtJFxzmn7hzqYdTV0RUIy9oBsZtRtleAW00IRSIBDfUu8ffLUIZZE2q0nGJUQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/basketry/-/basketry-0.2.0.tgz", + "integrity": "sha512-LLd6QdP/2Mnhm2YGDnW5m95/bYaAJJYuuNeDLL/V2ohptorfFJcpnJFrMrfXWMGCnoQDd4ICPTiayKLoXqCkUw==", "dev": true, "requires": { - "@basketry/ir": "^0.2.0-rc.0", + "@basketry/ir": "^0.2.0", "case": "^1.6.3", "chalk": "^4.1.2", "ts-node": "^10.7.0", diff --git a/package.json b/package.json index 3238c59..0518c60 100644 --- a/package.json +++ b/package.json @@ -34,13 +34,13 @@ "homepage": "https://github.com/basketry/ir#readme", "funding": "https://github.com/sponsors/basketry", "devDependencies": { - "@basketry/json-schema": "^0.2.0-rc.0", - "@basketry/typescript": "^0.2.0-rc.0", + "@basketry/json-schema": "^0.2.0", + "@basketry/typescript": "^0.2.2", "@types/jest": "^27.4.0", "@types/node": "^17.0.10", "@typescript-eslint/eslint-plugin": "^5.8.1", "@typescript-eslint/parser": "^5.8.1", - "basketry": "^0.2.0-rc.1", + "basketry": "^0.2.0", "eslint": "^8.5.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.3", From 3ae2d522ca5a6394e008ab95d09b12871b909524 Mon Sep 17 00:00:00 2001 From: Steve Konves Date: Sat, 23 Aug 2025 10:57:20 -0700 Subject: [PATCH 3/3] Define RPC-based plugins --- src/generated/types.ts | 428 +++++++++++++++++++++++++- src/index.ts | 2 +- src/schema.json | 677 +++++++++++++++++++++++++++++++++-------- src/validator.ts | 120 +++++++- 4 files changed, 1066 insertions(+), 161 deletions(-) diff --git a/src/generated/types.ts b/src/generated/types.ts index fa3e87e..fd758c2 100644 --- a/src/generated/types.ts +++ b/src/generated/types.ts @@ -1,5 +1,5 @@ /** - * This code was generated by @basketry/typescript@0.2.0-rc.0 + * This code was generated by @basketry/typescript@0.2.2 * * Changes to this file may cause incorrect behavior and will be lost if * the code is regenerated. @@ -46,6 +46,8 @@ export type Primitive = | 'binary' | 'untyped'; +export type Severity = 'error' | 'warning' | 'info'; + export type ApiKeyScheme = { kind: 'ApiKeyScheme'; type: ApiKeySchemeType; @@ -123,6 +125,22 @@ export type BasicSchemeType = { loc?: string; }; +/** + * The Basketry context provides metadata about the current Basketry execution + * environment. This context is useful for resolving relative paths, debugging + * plugin behavior, or adapting plugin behavior based on CLI invocation details. + */ +export type BasketryContext = { + /** Absolute path to the Basketry configuration file that initiated this run. */ + configPath?: string; + + /** + * Absolute path to the root of the Basketry project. All relative paths (e.g. + * source files, outputs) are resolved against this directory. + */ + projectDirectory: string; +}; + /** Represents a boolean value exactly as it appears in the original source document. */ export type BooleanLiteral = { kind: 'BooleanLiteral'; @@ -289,6 +307,126 @@ export type EnumMember = { meta?: MetaValue[]; }; +/** + * The `ErrorResponse` object defines the structure of a JSON-RPC response returned + * when a request fails. It adheres to the [JSON-RPC 2.0 + * specification](https://www.jsonrpc.org/specification) and conveys error + * information instead of a result. + */ +export type ErrorResponse = { + /** The JSON-RPC protocol version. */ + jsonrpc: '2.0'; + + /** + * Echoes the ID of the corresponding request. Used by the caller to correlate + * responses. If the request could not be parsed, this value SHOULD be `null`. + */ + id: ErrorResponseId; + + /** The error object providing details about the failure. */ + error: RpcError; +}; + +/** The `File` object represents a single file produced by a generator. */ +export type File = { + /** + * The path to the file, relative to the project directory. This MUST include the + * file name and extension. The array form allows paths to be represented in a + * normalized, platform-independent manner (e.g., ["src", "models", "user.ts"]). + */ + path: string[]; + + /** + * The complete textual contents of the file. Generators MUST encode the entire file + * body as a UTF-8 string. + */ + contents: string; +}; + +/** + * The `GenerateParams` object defines the input parameters passed to the + * `basketry.generate` method. It provides the generator with the service definition + * to render. + */ +export type GenerateParams = { + /** + * Contains the parsed `Service` and generator-specific options. This is the primary + * input for code generation. + */ + context: GeneratorContext; + + /** Contains runtime metadata. */ + basketry: BasketryContext; +}; + +/** + * The `GenerateRequest` object defines the structure of a JSON-RPC request for + * invoking the `basketry.generate` method. This method is called by the Basketry + * CLI or orchestrator to execute one or more generators against a parsed `Service`. + * It adheres to the [JSON-RPC 2.0 + * specification](https://www.jsonrpc.org/specification) and includes metadata and + * parameters needed to perform the generation operation. + */ +export type GenerateRequest = { + /** The JSON-RPC protocol version. */ + jsonrpc: '2.0'; + + /** + * A unique identifier for this request. The response from the plugin will include + * this same ID, allowing the caller to correlate requests and responses. + */ + id: RequestId; + + /** The name of the RPC method to invoke. */ + method: 'basketry.generate'; + + /** The parameters specific to the generate operation. */ + params: GenerateParams; +}; + +/** + * The `GenerateResponse` object represents the response payload returned from the + * `basketry.generate` method. It adheres to the [JSON-RPC 2.0 + * specification](https://www.jsonrpc.org/specification) and contains the files + * produced by the generator. + */ +export type GenerateResponse = { + /** The JSON-RPC protocol version. */ + jsonrpc: '2.0'; + + /** Echoes the ID of the corresponding request. Used by the caller to correlate responses. */ + id: RequestId; + + /** The outcome of the generation operation, including all files produced. */ + result: GenerateResult; +}; + +/** The `GenerateResult` object encapsulates the output of a generator. */ +export type GenerateResult = { + /** An array of files produced by the generator. If no files are generated, this array MUST be empty. */ + files: File[]; +}; + +/** + * The `GeneratorContext` object provides the inputs required by a generator to + * produce output artifacts. + */ +export type GeneratorContext = { + /** + * The intermediate representation (IR) of the service, produced by a parser. This + * is the input used by the generator to create code, documentation, or other + * artifacts. + */ + service: Service; + + /** + * Generator-specific configuration values. The structure and semantics of this + * field are defined by the generator itself. If omitted, empty, or improperly + * structured, the generator MUST apply its default behavior + */ + options?: any; +}; + /** * Represents the format of an array in an HTTP request or response as it appears in * the original source document. @@ -811,6 +949,116 @@ export type Parameter = { meta?: MetaValue[]; }; +/** + * The input payload passed to a plugin’s `basketry.parse` method. This object + * contains both the raw content to be parsed and metadata about the execution + * environment. Plugin authors should use this object to extract all necessary + * information to perform the parse operation. + */ +export type ParseParams = { + /** + * Contains the source text and its associated file path. This is the content the + * plugin is expected to parse. + */ + context: ParserContext; + + /** Contains runtime metadata. */ + basketry: BasketryContext; +}; + +/** + * The Parser context object provides the core input to the plugin's parser: the raw + * source file and its path. This context isolates the entrypoint file that should + * be parsed and serves as the root for resolving any references to additional files + * (if applicable). + */ +export type ParserContext = { + /** The relative path to the entrypoint file, from the root of the project directory. */ + sourcePath: string; + + /** The full text content of the entrypoint file. */ + sourceContent: string; +}; + +/** + * The `ParseRequest` object defines the structure of a JSON-RPC request for + * invoking the `basketry.parse` method. This method is called by the Basketry CLI + * or orchestrator to initiate parsing of one or more input files by a plugin. It + * adheres to the [JSON-RPC 2.0 + * specification](https://www.jsonrpc.org/specification) and includes metadata and + * parameters needed to perform the parse operation. + */ +export type ParseRequest = { + /** The JSON-RPC protocol version. */ + jsonrpc: '2.0'; + + /** + * A unique identifier for this request. The response from the plugin will include + * this same ID, allowing the caller to correlate requests and responses. + */ + id: RequestId; + + /** The name of the RPC method to invoke. */ + method: 'basketry.parse'; + + /** The parameters specific to the parse operation. */ + params: ParseParams; +}; + +/** + * The Parse response object represents the response payload returned from a + * plugin’s `basketry.parse` method. It adheres to the [JSON-RPC 2.0 + * specification](https://www.jsonrpc.org/specification) and contains the result of + * the parsing operation including both the produced service definition and any + * violations encountered. + */ +export type ParseResponse = { + /** The JSON-RPC protocol version. */ + jsonrpc: '2.0'; + + /** Echoes the ID of the corresponding request. Used by the caller to correlate responses. */ + id: RequestId; + + /** + * The result of the parse operation, including a service definition (if successful) + * and a list of violations (if any). + */ + result: ParseResult; +}; + +/** + * The parse result object encapsulates the output of a parsing operation. This + * includes the intermediate representation (IR) of the parsed service and any + * violations reported during the parsing process. + */ +export type ParseResult = { + /** + * The parsed intermediate representation of the service. This is the structured + * output that downstream Basketry components will consume. This field is typically + * `undefined` if parsing fails entirely + */ + service?: Service; + + /** + * A list of violations encountered during parsing. Even if parsing is successful, + * this array may include warnings or informational messages to aid in debugging or + * quality assurance. If no issues were found, this should be an empty array. + */ + violations: Violation[]; +}; + +/** The `Position` object defines a single point in a source document. */ +export type Position = { + /** The 1-based line number of the position. The first line in a document is `1`. */ + line: number; + + /** The 1-based column number of the position within the line. The first column is `1`. */ + column: number; + + /** The 0-based character offset from the start of the document. The first character is `0`. */ + offset: number; +}; + export type PrimitiveLiteral = { kind: 'PrimitiveLiteral'; value: Primitive; @@ -913,6 +1161,20 @@ export type Protocols = { http?: HttpRoute[]; }; +/** + * The `Range` object defines a span within a source document. A `Range` is the + * decoded representation of the `loc` property found in the `Service` IR and + * provides precise information about where a violation or element occurs in the + * source text. + */ +export type Range = { + /** The position of the first character in the range. */ + start: Position; + + /** The position of the last character in the range. */ + end: Position; +}; + /** * A ReturnValue defines the output produced by a method, specifying the value’s * data shape and constraints. @@ -933,6 +1195,44 @@ export type ReturnValue = { meta?: MetaValue[]; }; +export type RpcError = { + /** + * A machine-readable error code that identifies the type of error. Codes SHOULD + * follow the [JSON-RPC 2.0 standard error + * codes](https://www.jsonrpc.org/specification#error_object) when applicable, but + * MAY include custom values for Basketry-specific conditions. + */ + code: number; + + /** + * A short human-readable description of the error. The message SHOULD be limited to + * a concise single sentence. + */ + message: string; + + /** + * An optional value that contains additional information about the error. The + * structure of this value MAY be determined by the plugin. + */ + data?: any; +}; + +/** The `RuleContext` object provides the inputs that a rule evaluates during validation. */ +export type RuleContext = { + /** + * The intermediate representation (IR) of the service, produced by a parser. This + * is the input that rules validate against. + */ + service: Service; + + /** + * Rule-specific configuration values. The structure and semantics of this field are + * defined by the rule itself. If omitted, empty, or improperly structured, the rule + * MUST apply its default behavior + */ + options?: any; +}; + export type SecurityOption = { kind: 'SecurityOption'; schemes: SecurityScheme[]; @@ -942,19 +1242,12 @@ export type SecurityOption = { }; /** - * Intermediate Representation (IR) of a service. The Basketry Intermediate - * Representation (IR) defines a structured, machine-oriented format that abstracts - * over various Interface Definition Languages (IDLs) and Service Description - * Languages (SDLs). This standardized, language-agnostic schema allows tooling to - * consistently interpret the core attributes and behaviors of a service, - * independent of its underlying definition format. - * - * Basketry IR is optimized for automated workflows, enabling code generation, - * documentation, validation, and custom analysis through reusable components - * without requiring developers to manually interact with the raw representation. - * While a typical Basketry pipeline translates human-friendly specifications (such - * as OpenAPI) into this intermediate format, the IR serves as a foundational layer - * for building new generators, rules, and other automation tools. + * The `Service` object is the root of a Basketry Intermediate Representation (IR) + * document. It defines the core attributes of an API service, including its name, + * version, source documents, and all types and interfaces it contains. The + * `Service` object provides a canonical, language-agnostic model of the service + * definition that plugins such as generators and rules can consume without + * requiring access to the original source files. */ export type Service = { kind: 'Service'; @@ -1176,6 +1469,110 @@ export type UntypedLiteral = { loc?: string; }; +/** The `ValidateParams` object defines the input parameters passed to the `basketry.validate` method. */ +export type ValidateParams = { + /** Contains the `Service` to be validated and any rule-specific options. */ + context: RuleContext; + + /** Contains runtime metadata. */ + basketry: BasketryContext; +}; + +/** + * The `ValidateRequest` object defines the structure of a JSON-RPC request for + * invoking the `basketry.validate` method. This method is called by the Basketry + * CLI or orchestrator to execute one or more rules against a parsed `Service`. It + * adheres to the [JSON-RPC 2.0 + * specification](https://www.jsonrpc.org/specification) and includes metadata and + * parameters needed to perform the validation operation. + */ +export type ValidateRequest = { + /** The JSON-RPC protocol version. */ + jsonrpc: '2.0'; + + /** + * A unique identifier for this request. The response from the plugin will include + * this same ID, allowing the caller to correlate requests and responses. + */ + id: RequestId; + + /** The name of the RPC method to invoke. */ + method: 'basketry.validate'; + + /** The parameters specific to the validate operation. */ + params: ValidateParams; +}; + +/** + * The `ValidateResponse` object represents the response payload returned from the + * `basketry.validate` method. It adheres to the [JSON-RPC 2.0 + * specification](https://www.jsonrpc.org/specification) and contains the result of + * executing a rule against a service definition. + */ +export type ValidateResponse = { + /** The JSON-RPC protocol version. */ + jsonrpc: '2.0'; + + /** Echoes the ID of the corresponding request. Used by the caller to correlate responses. */ + id: RequestId; + + /** The outcome of the validation, including all violations produced by the rule. */ + result: ValidateResult; +}; + +/** The `ValidateResult` object encapsulates the outcome of a rule’s validation run. */ +export type ValidateResult = { + /** A list of violations produced by the rule. If no violations are found, this array MUST be empty. */ + violations: Violation[]; +}; + +/** + * A Violation represents a problem encountered during parsing or rule evaluation. + * It includes metadata to help developers and systems pinpoint and understand + * issues within the source document. + */ +export type Violation = { + /** + * The path to the source document that contains the violation. This path is + * relative to the project directory. + */ + sourcePath: string; + + /** The range of the violation in the source document. */ + range: Range; + + /** + * A human-readable explanation of the violation, suitable for display in CLI + * output, logs, or developer tools. + */ + message: string; + + /** + * Indicates the impact of the violation. Use `"error"` for blocking issues, + * `"warning"` for non-blocking recommendations, and `"info"` for helpful guidance. + */ + severity: Severity; + + /** + * A unique identifier for the type of violation. This value SHOULD be a short, + * machine-readable string that can be used to identify the violation. + */ + code: string; + + /** + * A URL that provides additional documentation or context for the violation. MUST + * be a valid URI if provided. Useful for pointing to rule definitions, spec + * references, or troubleshooting guides. + */ + link?: string; +}; + +/** + * Echoes the ID of the corresponding request. Used by the caller to correlate + * responses. If the request could not be parsed, this value SHOULD be `null`. + */ +export type ErrorResponseId = string | number | any; + export type MemberValue = PrimitiveValue | ComplexValue; export function isPrimitiveValue(obj: MemberValue): obj is PrimitiveValue { @@ -1261,6 +1658,9 @@ export type PrimitiveValueDefault = | BooleanLiteral | NullLiteral; +/** A request id */ +export type RequestId = string | number; + export type SecurityScheme = BasicScheme | ApiKeyScheme | OAuth2Scheme; export function isBasicScheme(obj: SecurityScheme): obj is BasicScheme { diff --git a/src/index.ts b/src/index.ts index a6f32e1..4fb9256 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ export * from './generated/types'; -export { validate } from './validator'; +export * from './validator'; diff --git a/src/schema.json b/src/schema.json index 35e8d94..bd19292 100644 --- a/src/schema.json +++ b/src/schema.json @@ -1,73 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "type": "object", - "title": "Service", - "description": "Intermediate Representation (IR) of a service. The Basketry Intermediate Representation (IR) defines a structured, machine-oriented format that abstracts over various Interface Definition Languages (IDLs) and Service Description Languages (SDLs). This standardized, language-agnostic schema allows tooling to consistently interpret the core attributes and behaviors of a service, independent of its underlying definition format.\n\nBasketry IR is optimized for automated workflows, enabling code generation, documentation, validation, and custom analysis through reusable components without requiring developers to manually interact with the raw representation. While a typical Basketry pipeline translates human-friendly specifications (such as OpenAPI) into this intermediate format, the IR serves as a foundational layer for building new generators, rules, and other automation tools.", - "required": [ - "kind", - "basketry", - "title", - "majorVersion", - "sourcePaths", - "interfaces", - "types", - "enums", - "unions" - ], - "properties": { - "$schema": { "type": "string" }, - "kind": { "type": "string", "const": "Service" }, - "basketry": { - "description": "This string MUST be the version number of the Basketry Specification that the Intermediate Representation uses. The `basketry` field SHOULD be used by tooling to interpret the IR document. This is not related to the Service `majorVersion` string.", - "type": "string", - "const": "0.2" - }, - "title": { - "description": "The title of the service", - "$ref": "#/definitions/string" - }, - - "majorVersion": { - "description": "The major version of the Service (which is distinct from the Basketry Specification version).", - "$ref": "#/definitions/integer" - }, - "sourcePaths": { - "type": "array", - "description": "An array of paths to the original source documents for this service. These paths are relative to the directory of the config file that produced the IR. All locations in the Intermediate Representation refer to ranges within these source documents.", - "items": { "type": "string" } - }, - "interfaces": { - "description": "An array of Interfaces defined in this Service.", - "type": "array", - "items": { - "$ref": "#/definitions/interface" - } - }, - "types": { - "description": "An array of Types defined in this Service.", - "type": "array", - "items": { "$ref": "#/definitions/type" } - }, - "enums": { - "description": "An array of Enums defined in this Service.", - "type": "array", - "items": { "$ref": "#/definitions/enum" } - }, - "unions": { - "description": "An array of Unions defined in this Service.", - "type": "array", - "items": { "$ref": "#/definitions/union" } - }, - "loc": { - "description": "The encoded location of the service in the source document(s).", - "$ref": "#/definitions/range" - }, - "meta": { - "description": "An array of metadata values for the service.", - "$ref": "#/definitions/meta" - } - }, - "additionalProperties": false, "definitions": { "meta": { "type": "array", @@ -102,6 +34,489 @@ } }, + "service": { + "type": "object", + "title": "Service", + "description": "The `Service` object is the root of a Basketry Intermediate Representation (IR) document. It defines the core attributes of an API service, including its name, version, source documents, and all types and interfaces it contains. The `Service` object provides a canonical, language-agnostic model of the service definition that plugins such as generators and rules can consume without requiring access to the original source files.", + "required": [ + "kind", + "basketry", + "title", + "majorVersion", + "sourcePaths", + "interfaces", + "types", + "enums", + "unions" + ], + "properties": { + "$schema": { "type": "string" }, + "kind": { "type": "string", "const": "Service" }, + "basketry": { + "description": "This string MUST be the version number of the Basketry Specification that the Intermediate Representation uses. The `basketry` field SHOULD be used by tooling to interpret the IR document. This is not related to the Service `majorVersion` string.", + "type": "string", + "const": "0.2" + }, + "title": { + "description": "The title of the service", + "$ref": "#/definitions/string" + }, + + "majorVersion": { + "description": "The major version of the Service (which is distinct from the Basketry Specification version).", + "$ref": "#/definitions/integer" + }, + "sourcePaths": { + "type": "array", + "description": "An array of paths to the original source documents for this service. These paths are relative to the directory of the config file that produced the IR. All locations in the Intermediate Representation refer to ranges within these source documents.", + "items": { "type": "string" } + }, + "interfaces": { + "description": "An array of Interfaces defined in this Service.", + "type": "array", + "items": { + "$ref": "#/definitions/interface" + } + }, + "types": { + "description": "An array of Types defined in this Service.", + "type": "array", + "items": { "$ref": "#/definitions/type" } + }, + "enums": { + "description": "An array of Enums defined in this Service.", + "type": "array", + "items": { "$ref": "#/definitions/enum" } + }, + "unions": { + "description": "An array of Unions defined in this Service.", + "type": "array", + "items": { "$ref": "#/definitions/union" } + }, + "loc": { + "description": "The encoded location of the service in the source document(s).", + "$ref": "#/definitions/encodedRange" + }, + "meta": { + "description": "An array of metadata values for the service.", + "$ref": "#/definitions/meta" + } + }, + "additionalProperties": false + }, + "severity": { + "type": "string", + "enum": ["error", "warning", "info"] + }, + "violation": { + "type": "object", + "description": "A Violation represents a problem encountered during parsing or rule evaluation. It includes metadata to help developers and systems pinpoint and understand issues within the source document.", + "required": ["sourcePath", "range", "message", "severity", "code"], + "properties": { + "sourcePath": { + "description": "The path to the source document that contains the violation. This path is relative to the project directory.", + "type": "string" + }, + "range": { + "description": "The range of the violation in the source document.", + "$ref": "#/definitions/range" + }, + "message": { + "description": "A human-readable explanation of the violation, suitable for display in CLI output, logs, or developer tools.", + "type": "string" + }, + "severity": { + "description": "Indicates the impact of the violation. Use `\"error\"` for blocking issues, `\"warning\"` for non-blocking recommendations, and `\"info\"` for helpful guidance.", + "$ref": "#/definitions/severity" + }, + "code": { + "description": "A unique identifier for the type of violation. This value SHOULD be a short, machine-readable string that can be used to identify the violation.", + "type": "string" + }, + "link": { + "description": "A URL that provides additional documentation or context for the violation. MUST be a valid URI if provided. Useful for pointing to rule definitions, spec references, or troubleshooting guides.", + "type": "string" + } + } + }, + "range": { + "type": "object", + "description": "The `Range` object defines a span within a source document. A `Range` is the decoded representation of the `loc` property found in the `Service` IR and provides precise information about where a violation or element occurs in the source text.", + "required": ["start", "end"], + "properties": { + "start": { + "description": "The position of the first character in the range.", + "$ref": "#/definitions/position" + }, + "end": { + "description": "The position of the last character in the range.", + "$ref": "#/definitions/position" + } + } + }, + "position": { + "type": "object", + "description": "The `Position` object defines a single point in a source document.", + "required": ["line", "column", "offset"], + "properties": { + "line": { + "description": "The 1-based line number of the position. The first line in a document is `1`.", + "type": "integer" + }, + "column": { + "description": "The 1-based column number of the position within the line. The first column is `1`.", + "type": "integer" + }, + "offset": { + "description": "The 0-based character offset from the start of the document. The first character is `0`.", + "type": "integer" + } + } + }, + + "requestId": { + "description": "A request id", + "oneOf": [{ "type": "string" }, { "type": "number" }] + }, + "errorResponse": { + "type": "object", + "description": "The `ErrorResponse` object defines the structure of a JSON-RPC response returned when a request fails. It adheres to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification) and conveys error information instead of a result.", + "required": ["jsonrpc", "id", "error"], + "properties": { + "jsonrpc": { + "type": "string", + "description": "The JSON-RPC protocol version.", + "const": "2.0" + }, + "id": { + "description": "Echoes the ID of the corresponding request. Used by the caller to correlate responses. If the request could not be parsed, this value SHOULD be `null`.", + "oneOf": [ + { "type": "string" }, + { "type": "number" }, + { "type": "null" } + ] + }, + "error": { + "description": "The error object providing details about the failure.", + "$ref": "#/definitions/rpcError" + } + } + }, + "rpcError": { + "type": "object", + "required": ["code", "message"], + "properties": { + "code": { + "description": "A machine-readable error code that identifies the type of error. Codes SHOULD follow the [JSON-RPC 2.0 standard error codes](https://www.jsonrpc.org/specification#error_object) when applicable, but MAY include custom values for Basketry-specific conditions.", + "type": "integer" + }, + "message": { + "description": "A short human-readable description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + }, + "data": { + "description": "An optional value that contains additional information about the error. The structure of this value MAY be determined by the plugin." + } + } + }, + "basketryContext": { + "type": "object", + "description": "The Basketry context provides metadata about the current Basketry execution environment. This context is useful for resolving relative paths, debugging plugin behavior, or adapting plugin behavior based on CLI invocation details.", + "required": ["configuration", "projectDirectory"], + "properties": { + "configPath": { + "description": "Absolute path to the Basketry configuration file that initiated this run.", + "type": "string" + }, + "projectDirectory": { + "description": "Absolute path to the root of the Basketry project. All relative paths (e.g. source files, outputs) are resolved against this directory.", + "type": "string" + } + } + }, + + "parserContext": { + "type": "object", + "description": "The Parser context object provides the core input to the plugin's parser: the raw source file and its path. This context isolates the entrypoint file that should be parsed and serves as the root for resolving any references to additional files (if applicable).", + "required": ["sourcePath", "sourceContent"], + "properties": { + "sourcePath": { + "description": "The relative path to the entrypoint file, from the root of the project directory.", + "type": "string" + }, + "sourceContent": { + "description": "The full text content of the entrypoint file.", + "type": "string" + } + } + }, + "parseRequest": { + "type": "object", + "description": "The `ParseRequest` object defines the structure of a JSON-RPC request for invoking the `basketry.parse` method. This method is called by the Basketry CLI or orchestrator to initiate parsing of one or more input files by a plugin. It adheres to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification) and includes metadata and parameters needed to perform the parse operation.", + "required": ["jsonrpc", "id", "method", "params"], + "properties": { + "jsonrpc": { + "type": "string", + "description": "The JSON-RPC protocol version.", + "const": "2.0" + }, + "id": { + "description": "A unique identifier for this request. The response from the plugin will include this same ID, allowing the caller to correlate requests and responses.", + "$ref": "#/definitions/requestId" + }, + "method": { + "type": "string", + "description": "The name of the RPC method to invoke.", + "const": "basketry.parse" + }, + "params": { + "description": "The parameters specific to the parse operation.", + "$ref": "#/definitions/parseParams" + } + } + }, + "parseParams": { + "type": "object", + "description": "The input payload passed to a plugin’s `basketry.parse` method. This object contains both the raw content to be parsed and metadata about the execution environment. Plugin authors should use this object to extract all necessary information to perform the parse operation.", + "required": ["context", "basketry"], + "properties": { + "context": { + "description": "Contains the source text and its associated file path. This is the content the plugin is expected to parse.", + "$ref": "#/definitions/parserContext" + }, + "basketry": { + "description": "Contains runtime metadata.", + "$ref": "#/definitions/basketryContext" + } + } + }, + "parseResponse": { + "type": "object", + "description": "The Parse response object represents the response payload returned from a plugin’s `basketry.parse` method. It adheres to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification) and contains the result of the parsing operation including both the produced service definition and any violations encountered.", + "required": ["jsonrpc", "id", "result"], + "properties": { + "jsonrpc": { + "type": "string", + "description": "The JSON-RPC protocol version.", + "const": "2.0" + }, + "id": { + "description": "Echoes the ID of the corresponding request. Used by the caller to correlate responses.", + "$ref": "#/definitions/requestId" + }, + "result": { + "description": "The result of the parse operation, including a service definition (if successful) and a list of violations (if any).", + "$ref": "#/definitions/parseResult" + } + } + }, + "parseResult": { + "type": "object", + "description": "The parse result object encapsulates the output of a parsing operation. This includes the intermediate representation (IR) of the parsed service and any violations reported during the parsing process.", + "required": ["violations"], + "properties": { + "service": { + "description": "The parsed intermediate representation of the service. This is the structured output that downstream Basketry components will consume. This field is typically `undefined` if parsing fails entirely", + "$ref": "#/definitions/service" + }, + "violations": { + "type": "array", + "description": "A list of violations encountered during parsing. Even if parsing is successful, this array may include warnings or informational messages to aid in debugging or quality assurance. If no issues were found, this should be an empty array.", + "items": { "$ref": "#/definitions/violation" } + } + } + }, + + "ruleContext": { + "type": "object", + "description": "The `RuleContext` object provides the inputs that a rule evaluates during validation.", + "required": ["service"], + "properties": { + "service": { + "description": "The intermediate representation (IR) of the service, produced by a parser. This is the input that rules validate against.", + "$ref": "#/definitions/service" + }, + "options": { + "description": "Rule-specific configuration values. The structure and semantics of this field are defined by the rule itself. If omitted, empty, or improperly structured, the rule MUST apply its default behavior" + } + } + }, + "validateRequest": { + "type": "object", + "description": "The `ValidateRequest` object defines the structure of a JSON-RPC request for invoking the `basketry.validate` method. This method is called by the Basketry CLI or orchestrator to execute one or more rules against a parsed `Service`. It adheres to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification) and includes metadata and parameters needed to perform the validation operation.", + "required": ["jsonrpc", "id", "method", "params"], + "properties": { + "jsonrpc": { + "type": "string", + "description": "The JSON-RPC protocol version.", + "const": "2.0" + }, + "id": { + "description": "A unique identifier for this request. The response from the plugin will include this same ID, allowing the caller to correlate requests and responses.", + "$ref": "#/definitions/requestId" + }, + "method": { + "type": "string", + "description": "The name of the RPC method to invoke.", + "const": "basketry.validate" + }, + "params": { + "description": "The parameters specific to the validate operation.", + "$ref": "#/definitions/validateParams" + } + } + }, + "validateParams": { + "type": "object", + "description": "The `ValidateParams` object defines the input parameters passed to the `basketry.validate` method.", + "required": ["context", "basketry"], + "properties": { + "context": { + "description": "Contains the `Service` to be validated and any rule-specific options.", + "$ref": "#/definitions/ruleContext" + }, + "basketry": { + "description": "Contains runtime metadata.", + "$ref": "#/definitions/basketryContext" + } + } + }, + "validateResponse": { + "type": "object", + "description": "The `ValidateResponse` object represents the response payload returned from the `basketry.validate` method. It adheres to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification) and contains the result of executing a rule against a service definition.", + "required": ["jsonrpc", "id", "result"], + "properties": { + "jsonrpc": { + "type": "string", + "description": "The JSON-RPC protocol version.", + "const": "2.0" + }, + "id": { + "description": "Echoes the ID of the corresponding request. Used by the caller to correlate responses.", + "$ref": "#/definitions/requestId" + }, + "result": { + "description": "The outcome of the validation, including all violations produced by the rule.", + "$ref": "#/definitions/validateResult" + } + } + }, + "validateResult": { + "type": "object", + "description": "The `ValidateResult` object encapsulates the outcome of a rule’s validation run.", + "required": ["violations"], + "properties": { + "violations": { + "type": "array", + "description": "A list of violations produced by the rule. If no violations are found, this array MUST be empty.", + "items": { "$ref": "#/definitions/violation" } + } + } + }, + + "file": { + "type": "object", + "description": "The `File` object represents a single file produced by a generator.", + "required": ["path", "contents"], + "properties": { + "path": { + "description": "The path to the file, relative to the project directory. This MUST include the file name and extension. The array form allows paths to be represented in a normalized, platform-independent manner (e.g., [\"src\", \"models\", \"user.ts\"]).", + "type": "array", + "items": { "type": "string" } + }, + "contents": { + "description": "The complete textual contents of the file. Generators MUST encode the entire file body as a UTF-8 string.", + "type": "string" + } + } + }, + "generatorContext": { + "type": "object", + "description": "The `GeneratorContext` object provides the inputs required by a generator to produce output artifacts.", + "required": ["service"], + "properties": { + "service": { + "description": "The intermediate representation (IR) of the service, produced by a parser. This is the input used by the generator to create code, documentation, or other artifacts.", + "$ref": "#/definitions/service" + }, + "options": { + "description": "Generator-specific configuration values. The structure and semantics of this field are defined by the generator itself. If omitted, empty, or improperly structured, the generator MUST apply its default behavior" + } + } + }, + "generateRequest": { + "type": "object", + "description": "The `GenerateRequest` object defines the structure of a JSON-RPC request for invoking the `basketry.generate` method. This method is called by the Basketry CLI or orchestrator to execute one or more generators against a parsed `Service`. It adheres to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification) and includes metadata and parameters needed to perform the generation operation.", + "required": ["jsonrpc", "id", "method", "params"], + "properties": { + "jsonrpc": { + "type": "string", + "description": "The JSON-RPC protocol version.", + "const": "2.0" + }, + "id": { + "description": "A unique identifier for this request. The response from the plugin will include this same ID, allowing the caller to correlate requests and responses.", + "$ref": "#/definitions/requestId" + }, + "method": { + "type": "string", + "description": "The name of the RPC method to invoke.", + "const": "basketry.generate" + }, + "params": { + "description": "The parameters specific to the generate operation.", + "$ref": "#/definitions/generateParams" + } + } + }, + "generateParams": { + "type": "object", + "description": "The `GenerateParams` object defines the input parameters passed to the `basketry.generate` method. It provides the generator with the service definition to render.", + "required": ["context", "basketry"], + "properties": { + "context": { + "description": "Contains the parsed `Service` and generator-specific options. This is the primary input for code generation.", + "$ref": "#/definitions/generatorContext" + }, + "basketry": { + "description": "Contains runtime metadata.", + "$ref": "#/definitions/basketryContext" + } + } + }, + "generateResponse": { + "type": "object", + "description": "The `GenerateResponse` object represents the response payload returned from the `basketry.generate` method. It adheres to the [JSON-RPC 2.0 specification](https://www.jsonrpc.org/specification) and contains the files produced by the generator.", + "required": ["jsonrpc", "id", "result"], + "properties": { + "jsonrpc": { + "type": "string", + "description": "The JSON-RPC protocol version.", + "const": "2.0" + }, + "id": { + "description": "Echoes the ID of the corresponding request. Used by the caller to correlate responses.", + "$ref": "#/definitions/requestId" + }, + "result": { + "description": "The outcome of the generation operation, including all files produced.", + "$ref": "#/definitions/generateResult" + } + } + }, + "generateResult": { + "type": "object", + "description": "The `GenerateResult` object encapsulates the output of a generator.", + "required": ["files"], + "properties": { + "files": { + "type": "array", + "description": "An array of files produced by the generator. If no files are generated, this array MUST be empty.", + "items": { "$ref": "#/definitions/file" } + } + } + }, + "interface": { "type": "object", "description": "An Interface defines a cohesive set of related methods that collectively represent a functional contract within a service. Interfaces act as the primary grouping mechanism for operations and are protocol-agnostic by default. They provide a stable, language-neutral abstraction of service capabilities, ensuring that tooling can target a wide range of programming languages and frameworks without losing semantic meaning.", @@ -171,7 +586,7 @@ }, "loc": { "description": "The encoded location of the method in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the method.", @@ -205,7 +620,7 @@ }, "loc": { "description": "The encoded location of the parameter in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the parameter.", @@ -226,7 +641,7 @@ }, "loc": { "description": "The encoded location of the return value in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the return value.", @@ -270,7 +685,7 @@ }, "loc": { "description": "The encoded location of the type in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the type.", @@ -304,7 +719,7 @@ }, "loc": { "description": "The encoded location of the property in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the property.", @@ -334,7 +749,7 @@ }, "loc": { "description": "The encoded location of the map properties in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the map properties.", @@ -355,7 +770,7 @@ }, "loc": { "description": "The encoded location of the map key in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the map key.", @@ -376,7 +791,7 @@ }, "loc": { "description": "The encoded location of the map key in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the map key.", @@ -413,7 +828,7 @@ }, "loc": { "description": "The encoded location of the enum in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the enum.", @@ -443,7 +858,7 @@ }, "loc": { "description": "The encoded location of the enum member in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the enum member.", @@ -472,7 +887,7 @@ "description": "In an inclusive disjunction, a value MUST fully match at least one union member and MAY also match others. In an exclusive disjunction, a value MUST fully match exactly one union member and MUST NOT match any others.", "$ref": "#/definitions/disjunctionKind" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -511,7 +926,7 @@ }, "loc": { "description": "The encoded location of the union in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the union.", @@ -551,7 +966,7 @@ }, "loc": { "description": "The encoded location of the union in the source document(s).", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" }, "meta": { "description": "An array of metadata values for the union.", @@ -585,7 +1000,7 @@ "properties": { "kind": { "type": "string", "const": "PrimitiveLiteral" }, "value": { "$ref": "#/definitions/primitive" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -707,7 +1122,7 @@ "properties": { "kind": { "type": "string", "const": "HttpStatusCodeLiteral" }, "value": { "type": "integer", "minimum": 100, "maximum": 599 }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -721,7 +1136,7 @@ "description": "The HTTP verb as a lowercase string. Generators MAY represent this value in a language- or platform-idiomatic casing.", "$ref": "#/definitions/httpVerb" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -736,7 +1151,7 @@ "properties": { "kind": { "type": "string", "const": "HttpLocationLiteral" }, "value": { "$ref": "#/definitions/httpLocation" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -752,7 +1167,7 @@ "properties": { "kind": { "type": "string", "const": "HttpArrayFormatLiteral" }, "value": { "$ref": "#/definitions/httpArrayFormat" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -766,7 +1181,7 @@ "type": "array", "items": { "$ref": "#/definitions/httpMethod" } }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -798,7 +1213,7 @@ "type": "array", "items": { "$ref": "#/definitions/string" } }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -810,7 +1225,7 @@ "name": { "$ref": "#/definitions/string" }, "location": { "$ref": "#/definitions/httpLocationLiteral" }, "arrayFormat": { "$ref": "#/definitions/httpArrayFormatLiteral" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -824,7 +1239,7 @@ "type": "array", "items": { "$ref": "#/definitions/securityScheme" } }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } } }, "securityScheme": { @@ -850,14 +1265,14 @@ "type": "string", "const": "basic" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, "deprecated": { "$ref": "#/definitions/true" }, "name": { "$ref": "#/definitions/string" }, "description": { "$ref": "#/definitions/string" }, - "loc": { "$ref": "#/definitions/range" }, + "loc": { "$ref": "#/definitions/encodedRange" }, "meta": { "$ref": "#/definitions/meta" } }, "additionalProperties": false @@ -875,7 +1290,7 @@ "type": "string", "const": "apiKey" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -894,11 +1309,11 @@ "type": "string", "enum": ["header", "query", "cookie"] }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, - "loc": { "$ref": "#/definitions/range" }, + "loc": { "$ref": "#/definitions/encodedRange" }, "meta": { "$ref": "#/definitions/meta" } }, "additionalProperties": false @@ -913,7 +1328,7 @@ "required": ["value"], "properties": { "value": { "type": "string", "const": "oauth2" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -927,7 +1342,7 @@ "type": "array", "items": { "$ref": "#/definitions/oAuth2Flow" } }, - "loc": { "$ref": "#/definitions/range" }, + "loc": { "$ref": "#/definitions/encodedRange" }, "meta": { "$ref": "#/definitions/meta" } }, "additionalProperties": false @@ -953,7 +1368,7 @@ "required": ["value"], "properties": { "value": { "type": "string", "const": "implicit" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -964,7 +1379,7 @@ "type": "array", "items": { "$ref": "#/definitions/oAuth2Scope" } }, - "loc": { "$ref": "#/definitions/range" }, + "loc": { "$ref": "#/definitions/encodedRange" }, "meta": { "$ref": "#/definitions/meta" } }, "additionalProperties": false @@ -979,7 +1394,7 @@ "required": ["value"], "properties": { "value": { "type": "string", "const": "password" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -990,7 +1405,7 @@ "type": "array", "items": { "$ref": "#/definitions/oAuth2Scope" } }, - "loc": { "$ref": "#/definitions/range" }, + "loc": { "$ref": "#/definitions/encodedRange" }, "meta": { "$ref": "#/definitions/meta" } }, "additionalProperties": false @@ -1005,7 +1420,7 @@ "required": ["value"], "properties": { "value": { "type": "string", "const": "clientCredentials" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1016,7 +1431,7 @@ "type": "array", "items": { "$ref": "#/definitions/oAuth2Scope" } }, - "loc": { "$ref": "#/definitions/range" }, + "loc": { "$ref": "#/definitions/encodedRange" }, "meta": { "$ref": "#/definitions/meta" } }, "additionalProperties": false @@ -1031,7 +1446,7 @@ "required": ["value"], "properties": { "value": { "type": "string", "const": "authorizationCode" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1043,7 +1458,7 @@ "type": "array", "items": { "$ref": "#/definitions/oAuth2Scope" } }, - "loc": { "$ref": "#/definitions/range" }, + "loc": { "$ref": "#/definitions/encodedRange" }, "meta": { "$ref": "#/definitions/meta" } }, "additionalProperties": false @@ -1059,7 +1474,7 @@ "items": { "$ref": "#/definitions/string" } }, "deprecated": { "$ref": "#/definitions/true" }, - "loc": { "$ref": "#/definitions/range" }, + "loc": { "$ref": "#/definitions/encodedRange" }, "meta": { "$ref": "#/definitions/meta" } }, "additionalProperties": false @@ -1075,7 +1490,7 @@ "value": { "type": "string" }, "loc": { "description": "The location of this in the doc.", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false @@ -1088,7 +1503,7 @@ "properties": { "kind": { "type": "string", "const": "IntegerLiteral" }, "value": { "type": "integer" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1100,7 +1515,7 @@ "properties": { "kind": { "type": "string", "const": "NumberLiteral" }, "value": { "type": "integer" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1112,7 +1527,7 @@ "properties": { "kind": { "type": "string", "const": "BooleanLiteral" }, "value": { "type": "boolean" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1124,7 +1539,7 @@ "properties": { "kind": { "type": "string", "const": "NullLiteral" }, "value": { "type": "null" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1136,7 +1551,7 @@ "properties": { "kind": { "type": "string", "const": "TrueLiteral" }, "value": { "type": "boolean", "const": true }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1150,7 +1565,7 @@ "value": { "type": "string", "minLength": 1 }, "loc": { "description": "The location of this in the doc.", - "$ref": "#/definitions/range" + "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false @@ -1163,7 +1578,7 @@ "properties": { "kind": { "type": "string", "const": "NonNegativeIntegerLiteral" }, "value": { "type": "integer", "minimum": 0, "multipleOf": 1 }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1175,12 +1590,12 @@ "properties": { "kind": { "type": "string", "const": "NonNegativeNumberLiteral" }, "value": { "type": "number", "minimum": 0 }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, - "range": { + "encodedRange": { "description": "The encoded location of this node in the source document(s).", "type": "string", "pattern": "^(\\d+:)((\\d+;){2}(\\d+)|(\\d+;){4}(\\d+)|(\\d+;){5}(\\d+))$" @@ -1218,7 +1633,7 @@ "const": "StringMaxLength" }, "length": { "$ref": "#/definitions/nonNegativeInteger" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1234,7 +1649,7 @@ "const": "StringMinLength" }, "length": { "$ref": "#/definitions/nonNegativeInteger" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1250,7 +1665,7 @@ "const": "StringPattern" }, "pattern": { "$ref": "#/definitions/nonEmptyString" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1266,7 +1681,7 @@ "const": "StringFormat" }, "format": { "$ref": "#/definitions/nonEmptyString" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1282,7 +1697,7 @@ "const": "NumberMultipleOf" }, "value": { "$ref": "#/definitions/nonNegativeNumber" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1298,7 +1713,7 @@ "const": "NumberGT" }, "value": { "$ref": "#/definitions/number" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1314,7 +1729,7 @@ "const": "NumberGTE" }, "value": { "$ref": "#/definitions/number" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1330,7 +1745,7 @@ "const": "NumberLT" }, "value": { "$ref": "#/definitions/number" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1346,7 +1761,7 @@ "const": "NumberLTE" }, "value": { "$ref": "#/definitions/number" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1362,7 +1777,7 @@ "const": "ArrayMaxItems" }, "max": { "$ref": "#/definitions/nonNegativeInteger" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1378,7 +1793,7 @@ "const": "ArrayMinItems" }, "min": { "$ref": "#/definitions/nonNegativeInteger" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1396,7 +1811,7 @@ "required": { "type": "boolean" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1423,7 +1838,7 @@ "const": "ObjectMinProperties" }, "min": { "$ref": "#/definitions/nonNegativeInteger" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1439,7 +1854,7 @@ "const": "ObjectMaxProperties" }, "max": { "$ref": "#/definitions/nonNegativeInteger" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false }, @@ -1454,7 +1869,7 @@ "const": "ObjectAdditionalProperties" }, "forbidden": { "$ref": "#/definitions/true" }, - "loc": { "$ref": "#/definitions/range" } + "loc": { "$ref": "#/definitions/encodedRange" } }, "additionalProperties": false } diff --git a/src/validator.ts b/src/validator.ts index 1cbab4d..f4dbb10 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -1,35 +1,125 @@ -import Ajv from 'ajv'; +import Ajv, { DefinedError, ValidateFunction } from 'ajv'; import * as schema from './schema.json'; -import { Service } from './generated/types'; +import { + GenerateRequest, + GenerateResponse, + ParseRequest, + ParseResponse, + Service, + ValidateRequest, + ValidateResponse, +} from './generated/types'; const ajv = new Ajv({ allErrors: true, strict: false }); -const runner = ajv.compile(schema); +// Use a local key as the base for resolving $refs +const KEY = 'ir-schema'; +ajv.addSchema(schema as object, KEY); + +/** @deprecated use PluginError */ export type ParserError = { code: 'PARSER_ERROR'; message: string; filepath?: string; }; -export function validate(service: any): { - service: Service | undefined; - errors: ParserError[]; -} { - const errors: ParserError[] = []; +/** Represents an error that occurs during plugin execution */ +export type PluginError = { + code: 'PLUGIN_ERROR'; + message: string; +}; + +/** @deprecated use validateService */ +export function validate(service: any) { + const { obj, errors } = validateSchema( + serviceRunner, + service, + 'PARSER_ERROR', + ); + return { service: obj, errors }; +} + +const serviceRunner = ajv.compile({ + $ref: `${KEY}#/definitions/service`, +}); +export function validateService(service: unknown) { + const { obj, errors } = validateSchema(serviceRunner, service); + return { service: obj, errors }; +} + +const parseRequestRunner = ajv.compile({ + $ref: `${KEY}#/definitions/parseRequest`, +}); +export function validateParserRequest(request: unknown) { + const { obj, errors } = validateSchema(parseRequestRunner, request); + return { request: obj, errors }; +} + +const parseResponseRunner = ajv.compile({ + $ref: `${KEY}#/definitions/parseResponse`, +}); +export function validateParseResponse(response: unknown) { + const { obj, errors } = validateSchema(parseResponseRunner, response); + return { response: obj, errors }; +} + +const validateRequestRunner = ajv.compile({ + $ref: `${KEY}#/definitions/validateRequest`, +}); +export function validateValidateRequest(request: unknown) { + const { obj, errors } = validateSchema(validateRequestRunner, request); + return { request: obj, errors }; +} + +const validateResponseRunner = ajv.compile({ + $ref: `${KEY}#/definitions/validateResponse`, +}); +export function validateValidateResponse(response: unknown) { + const { obj, errors } = validateSchema(validateResponseRunner, response); + return { response: obj, errors }; +} + +const generateRequestRunner = ajv.compile({ + $ref: `${KEY}#/definitions/generateRequest`, +}); +export function validateGenerateRequest(request: unknown) { + const { obj, errors } = validateSchema(generateRequestRunner, request); + return { request: obj, errors }; +} + +const generateResponseRunner = ajv.compile({ + $ref: `${KEY}#/definitions/generateResponse`, +}); +export function validateGenerateResponse(response: unknown) { + const { obj, errors } = validateSchema(generateResponseRunner, response); + return { response: obj, errors }; +} + +type ErrorByCode = { + PLUGIN_ERROR: PluginError; + PARSER_ERROR: ParserError; +}; + +function validateSchema( + runner: ValidateFunction, + obj: unknown, + errorCode: C = 'PLUGIN_ERROR' as C, +): { obj: T | undefined; errors: ErrorByCode[C][] } { + const errors: ErrorByCode[C][] = []; - const isValid = runner(service); + const isValid = runner(obj); if (!isValid) { - for (const error of runner.errors ?? []) { - const message = `Invalid IR: \`#${error.instancePath}\` ${error.message}`; + for (const error of (runner.errors ?? []) as DefinedError[]) { + const message = `Invalid schema: \`#${error.instancePath}\` ${error.message}`; errors.push({ - code: 'PARSER_ERROR', + code: errorCode, message, - }); + } as ErrorByCode[C]); } - return { service: undefined, errors }; + return { obj: undefined, errors }; } - return { service: service as Service, errors }; + return { obj: obj as T, errors }; }