一键导入
add-parser-adapter
Creates parser adapter packages for existing ApiDOM namespace packages and integrates them with apidom-reference
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Creates parser adapter packages for existing ApiDOM namespace packages and integrates them with apidom-reference
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Creates a new namespace package for a new API specification version in the ApiDOM monorepo
Updates apidom-ls configuration for a namespace package by analyzing the namespace structure and creating completion, documentation, and lint configurations
| name | add-parser-adapter |
| description | Creates parser adapter packages for existing ApiDOM namespace packages and integrates them with apidom-reference |
| disable-model-invocation | false |
| user-invocable | true |
Skill Name: add-parser-adapter
Description: Creates parser adapter packages for existing ApiDOM namespace packages and integrates them with apidom-reference.
This skill automates the process of creating parser adapter packages for API namespaces that don't yet have parsers. It discovers which apidom-ns-* packages are missing corresponding apidom-parser-adapter-* packages, allows you to select which ones to create, generates the complete parser adapter packages (both JSON and YAML), and integrates them with apidom-reference.
Use this skill when:
/add-namespace and need to add parsersBefore running this skill:
apidom-ns-* package must exist"openapi", "asyncapi", "arazzo")"3.1.0" for OpenAPI 3.1)OpenApi3_1Element, ArazzoSpecification1Element)npm run build at least once to ensure packages are availableScan the packages directory to identify namespaces without parser adapters:
Find all namespace packages:
ls -1 packages | grep "^apidom-ns-"
Find all parser adapter packages:
ls -1 packages | grep "^apidom-parser-adapter-"
Identify missing adapters by comparing:
apidom-ns-{spec}-{version}, check if these exist:
apidom-parser-adapter-{spec}-json-{version}apidom-parser-adapter-{spec}-yaml-{version}Special naming cases:
api-design-systems (no version)json-schema-{json|yaml}-{version}{spec}-{json|yaml}-{version}Build options list of missing adapters:
interface MissingAdapter {
namespace: string; // e.g., "apidom-ns-openapi-3-2"
spec: string; // e.g., "openapi"
version: string; // e.g., "3-2"
missingJSON: boolean; // true if JSON adapter missing
missingYAML: boolean; // true if YAML adapter missing
}
Present the missing adapters to the user using AskUserQuestion:
{
question: "Which namespace would you like to add parser adapters for?",
header: "Namespace",
multiSelect: false,
options: [
{
label: "OpenAPI 3.2 (JSON + YAML)",
description: "Creates apidom-parser-adapter-openapi-json-3-2 and apidom-parser-adapter-openapi-yaml-3-2"
},
{
label: "JSON Schema 2019-09 (JSON + YAML)",
description: "Creates apidom-parser-adapter-json-schema-json-2019-09 and apidom-parser-adapter-json-schema-yaml-2019-09"
}
// ... more options for each missing adapter
]
}
After selection, ask for additional information:
{
questions: [
{
question: "What is the specification version field name?",
header: "Field Name",
options: [
{ label: "openapi", description: "For OpenAPI specifications" },
{ label: "asyncapi", description: "For AsyncAPI specifications" },
{ label: "arazzo", description: "For Arazzo specifications" },
{ label: "Other", description: "Specify a custom field name" }
]
},
{
question: "What is the version pattern?",
header: "Version",
options: [
{ label: "3.2.0", description: "Exact version (e.g., OpenAPI 3.2.0)" },
{ label: "3.2.x", description: "Minor version range (3.2.0, 3.2.1, etc.)" },
{ label: "Other", description: "Specify custom version pattern" }
]
},
{
question: "What is the root specification element class name?",
header: "Root Element",
options: [
{ label: "From namespace exports", description: "Automatically discover from namespace package" }
]
}
]
}
Read the namespace package to extract required information:
Read namespace package.json:
const namespacePkg = JSON.parse(
readFile(`packages/apidom-ns-${spec}-${version}/package.json`)
);
Find root element class by reading the namespace exports:
OpenApi*.ts, AsyncApi*.ts, Arazzo*.ts)src/index.ts to identify exported element classesDetermine media types by reading src/media-types.ts
Extract version from namespace:
Create the JSON parser adapter package:
packages/apidom-parser-adapter-{spec}-json-{version}/
├── src/
│ ├── adapter.ts
│ └── media-types.ts
├── test/
│ ├── adapter.ts
│ ├── media-types.ts
│ ├── fixtures/
│ │ └── sample-spec.json
│ ├── mocha-bootstrap.ts
│ ├── .eslintrc
│ └── tsconfig.json
├── config/
│ ├── api-extractor/
│ │ └── api-extractor.json
│ └── webpack/
│ └── browser.config.js
├── package.json
├── tsconfig.json
├── tsconfig.declaration.json
└── README.md
{
"name": "@swagger-api/apidom-parser-adapter-{spec}-json-{version}",
"version": "1.0.0",
"description": "Parser adapter for parsing JSON documents into {Spec Name} {Version} namespace.",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"type": "module",
"sideEffects": false,
"unpkg": "./dist/apidom-parser-adapter-{spec}-json-{version}.browser.min.js",
"main": "./src/adapter.cjs",
"exports": {
"types": "./types/apidom-parser-adapter-{spec}-json-{version}.d.ts",
"import": "./src/adapter.mjs",
"require": "./src/adapter.cjs"
},
"types": "./types/apidom-parser-adapter-{spec}-json-{version}.d.ts",
"scripts": {
"build": "npm run clean && run-p --max-parallel ${CPU_CORES:-2} typescript:declaration build:es build:cjs build:umd:browser",
"build:es": "cross-env BABEL_ENV=es babel src --out-dir src --extensions '.ts' --out-file-extension '.mjs' --root-mode 'upward'",
"build:cjs": "cross-env BABEL_ENV=cjs babel src --out-dir src --extensions '.ts' --out-file-extension '.cjs' --root-mode 'upward'",
"build:umd:browser": "cross-env BABEL_ENV=browser webpack --config config/webpack/browser.config.js --progress",
"lint": "eslint ./",
"lint:fix": "eslint ./ --fix",
"clean": "rimraf --glob 'src/**/*.mjs' 'src/**/*.cjs' ./dist ./types",
"typescript:check-types": "tsc --noEmit && tsc -p ./test/tsconfig.json --noEmit",
"typescript:declaration": "tsc -p tsconfig.declaration.json && api-extractor run -l -c ./config/api-extractor/api-extractor.json",
"test": "NODE_ENV=test ts-mocha --exit",
"prepack": "copyfiles -u 3 ../../LICENSES/* LICENSES && copyfiles -u 2 ../../NOTICE .",
"postpack": "rimraf NOTICE LICENSES"
},
"repository": {
"type": "git",
"url": "git+https://github.com/swagger-api/apidom.git"
},
"author": "SmartBear",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime-corejs3": "^7.26.10",
"@swagger-api/apidom-core": "^1.2.2",
"@swagger-api/apidom-ns-{spec}-{version}": "^1.2.2",
"@swagger-api/apidom-parser-adapter-json": "^1.2.2",
"@types/ramda": "~0.30.0",
"ramda": "~0.30.0",
"ramda-adjunct": "^5.0.0"
},
"files": [
"src/**/*.mjs",
"src/**/*.cjs",
"dist/",
"types/apidom-parser-adapter-{spec}-json-{version}.d.ts",
"LICENSES",
"NOTICE",
"README.md",
"CHANGELOG.md"
]
}
import { propOr, omit } from 'ramda';
import { isNotUndefined } from 'ramda-adjunct';
import { ParseResultElement, createNamespace } from '@swagger-api/apidom-core';
import { parse as parseJSON, detect as detectJSON } from '@swagger-api/apidom-parser-adapter-json';
import {specName}Namespace, { {RootElement} } from '@swagger-api/apidom-ns-{spec}-{version}';
export { default as mediaTypes } from './media-types.ts';
/**
* Detection regex for {Spec Name} {Version}.
* Matches: "{specField}": "{majorVersion}.{minorVersion}.{patchVersion}"
*
* @public
*/
export const detectionRegExp =
/"{specField}"\s*:\s*"(?<version_json>{versionPattern})"/;
/**
* Detects if the source string is a {Spec Name} {Version} JSON document.
*
* @public
*/
export const detect = async (source: string): Promise<boolean> =>
detectionRegExp.test(source) && (await detectJSON(source));
/**
* Parses a {Spec Name} {Version} JSON document into ApiDOM.
*
* @public
*/
export const parse = async (
source: string,
options: Record<string, unknown> = {},
): Promise<ParseResultElement> => {
const refractorOpts: Record<string, unknown> = propOr({}, 'refractorOpts', options);
const parserOpts = omit(['refractorOpts'], options);
const parseResultElement = await parseJSON(source, parserOpts);
const { result } = parseResultElement;
if (isNotUndefined(result)) {
const {specName}SpecificationElement = {RootElement}.refract(result, refractorOpts);
{specName}SpecificationElement.classes.push('result');
parseResultElement.replaceResult({specName}SpecificationElement);
}
return parseResultElement;
};
/**
* @public
*/
export const namespace = createNamespace({specName}Namespace);
Template Variables:
{specName}: Namespace name in camelCase (e.g., arazzo, openApi, asyncApi){spec}: Spec identifier (e.g., arazzo, openapi, asyncapi){version}: Version identifier (e.g., 1, 3-2, 2019-09){RootElement}: Root element class name (e.g., ArazzoSpecification1Element, OpenApi3_2Element){Spec Name}: Human-readable spec name (e.g., Arazzo, OpenAPI, AsyncAPI){Version}: Human-readable version (e.g., 1.x.y, 3.2.x){specField}: Specification field name (e.g., arazzo, openapi, asyncapi){versionPattern}: Version regex pattern (e.g., 1\.(?:[1-9]\d*|0)\.(?:[1-9]\d*|0) for 1.x.y){majorVersion}: Major version number (e.g., 3)import { mediaTypes, {SpecName}MediaTypes } from '@swagger-api/apidom-ns-{spec}-{version}';
/**
* Media types for {Spec Name} {Version} JSON documents.
* Includes both generic JSON and specification-specific media types.
*
* @public
*/
const jsonMediaTypes = new {SpecName}MediaTypes(
...mediaTypes.filterByFormat('generic'),
...mediaTypes.filterByFormat('json'),
);
export default jsonMediaTypes;
Template Variables:
{SpecName}: Media types class name from namespace (e.g., ArazzoMediaTypes, OpenAPIMediaTypes)tsconfig.json:
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"composite": true
}
}
tsconfig.declaration.json:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "./types"
}
}
Copy from existing parser adapter (e.g., apidom-parser-adapter-arazzo-json-1):
test/adapter.ts:
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { assert, expect } from 'chai';
import { isParseResultElement, sexprs } from '@swagger-api/apidom-core';
import { is{RootElement} } from '@swagger-api/apidom-ns-{spec}-{version}';
import * as adapter from '../src/adapter.ts';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const jsonSpec = fs
.readFileSync(path.join(__dirname, 'fixtures', 'sample-spec.json'))
.toString();
describe('adapter', function () {
context('given definition in JSON format', function () {
specify('should detect proper media type', async function () {
assert.isTrue(await adapter.detect(jsonSpec));
});
specify('should detect minor version bump', async function () {
assert.isTrue(await adapter.detect('{{"{specField}": "{majorVersion}.{minorPlusOne}.0"}'));
});
specify('should detect patch version bump', async function () {
assert.isTrue(await adapter.detect('{{"{specField}": "{majorVersion}.{minorVersion}.1"}'));
});
specify('should not detect major version bump', async function () {
assert.isFalse(await adapter.detect('{{"{specField}": "{majorPlusOne}.0.0"}'));
});
});
it('should parse', async function () {
const parseResult = await adapter.parse(jsonSpec, { sourceMap: true });
assert.isTrue(isParseResultElement(parseResult));
assert.isTrue(is{RootElement}(parseResult.api));
expect(sexprs(parseResult)).toMatchSnapshot();
});
context('given zero byte empty file', function () {
specify('should return empty parse result', async function () {
const parseResult = await adapter.parse('', { sourceMap: true });
assert.isTrue(parseResult.isEmpty);
});
});
context('given non-zero byte empty file', function () {
specify('should return empty parser result', async function () {
const parseResult = await adapter.parse(' ', { sourceMap: true });
assert.isTrue(parseResult.isEmpty);
});
});
context('given invalid json file', function () {
specify('should return empty parser result', async function () {
const parseResult = await adapter.parse(' a ', { sourceMap: true });
assert.isTrue(parseResult.isEmpty);
});
});
context('detectionRegExp', function () {
specify('should reject invalid version ranges', function () {
assert.isFalse(adapter.detectionRegExp.test('{specField}: {majorVersion}.01.0'));
assert.isFalse(adapter.detectionRegExp.test('{specField}: {majorVersion}.0.x'));
assert.isFalse(adapter.detectionRegExp.test('{specField}: {majorPlusOne}.0.0'));
});
specify('should detect version ranges in forward compatible way', function () {
assert.isTrue(adapter.detectionRegExp.test('"{specField}": "{majorVersion}.{minorVersion}.0"'));
assert.isTrue(adapter.detectionRegExp.test('"{specField}": "{majorVersion}.{minorVersion}.145"'));
assert.isTrue(adapter.detectionRegExp.test('"{specField}": "{majorVersion}.{minorPlusOne}.0"'));
});
});
});
test/media-types.ts:
import { assert } from 'chai';
import ApiDOMParser from '@swagger-api/apidom-parser';
import * as adapter from '../src/adapter.ts';
/**
* IMPORTANT: Test media types through parser integration, not directly.
* Testing mediaTypes.latest() directly is discouraged - always test through
* the parser to ensure proper integration.
*/
describe('given adapter is used in parser', function () {
const parser = new ApiDOMParser().use(adapter);
context('given {Spec Name} {Version} definition in {format} format', function () {
specify('should find appropriate media type', async function () {
const mediaType = await parser.findMediaType('{{"{specField}": "{version}"}}');
assert.strictEqual(mediaType, '{expected-media-type}');
});
});
});
Critical: OpenAPI 3.2.0 testing revealed that testing mediaTypes.latest() directly is not recommended. Always test media type detection through the parser integration using ApiDOMParser.findMediaType().
test/fixtures/sample-spec.json:
Create the YAML parser adapter package (similar to JSON but with YAML-specific changes):
Same as JSON adapter but:
apidom-parser-adapter-{spec}-yaml-{version}-json- to -yaml-@swagger-api/apidom-parser-adapter-json to @swagger-api/apidom-parser-adapter-yaml-1-2import { propOr, omit } from 'ramda';
import { isNotUndefined } from 'ramda-adjunct';
import { ParseResultElement, createNamespace } from '@swagger-api/apidom-core';
import {
parse as parseYAML,
detect as detectYAML,
} from '@swagger-api/apidom-parser-adapter-yaml-1-2';
import {specName}Namespace, { {RootElement} } from '@swagger-api/apidom-ns-{spec}-{version}';
export { default as mediaTypes } from './media-types.ts';
/**
* Detection regex for {Spec Name} {Version}.
* Supports both YAML and JSON formats for flexibility.
*
* @public
*/
export const detectionRegExp =
/(?<YAML>^(["']?){specField}\2\s*:\s*(["']?)(?<version_yaml>{versionPattern})\3(?:\s+|$))|(?<JSON>"{specField}"\s*:\s*"(?<version_json>{versionPattern})")/m;
/**
* Detects if the source string is a {Spec Name} {Version} YAML document.
*
* @public
*/
export const detect = async (source: string): Promise<boolean> =>
detectionRegExp.test(source) && (await detectYAML(source));
/**
* Parses a {Spec Name} {Version} YAML document into ApiDOM.
*
* @public
*/
export const parse = async (
source: string,
options: Record<string, unknown> = {},
): Promise<ParseResultElement> => {
const refractorOpts: Record<string, unknown> = propOr({}, 'refractorOpts', options);
const parserOpts = omit(['refractorOpts'], options);
const parseResultElement = await parseYAML(source, parserOpts);
const { result } = parseResultElement;
if (isNotUndefined(result)) {
const {specName}SpecificationElement = {RootElement}.refract(result, refractorOpts);
{specName}SpecificationElement.classes.push('result');
parseResultElement.replaceResult({specName}SpecificationElement);
}
return parseResultElement;
};
/**
* @public
*/
export const namespace = createNamespace({specName}Namespace);
import { mediaTypes, {SpecName}MediaTypes } from '@swagger-api/apidom-ns-{spec}-{version}';
/**
* Media types for {Spec Name} {Version} YAML documents.
* Includes both generic YAML and specification-specific media types.
*
* @public
*/
const yamlMediaTypes = new {SpecName}MediaTypes(
...mediaTypes.filterByFormat('generic'),
...mediaTypes.filterByFormat('yaml'),
);
export default yamlMediaTypes;
Same as JSON adapter but:
test/adapter.ts - Update to test both YAML and JSON detection:
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { assert, expect } from 'chai';
import { isParseResultElement, sexprs } from '@swagger-api/apidom-core';
import { is{RootElement} } from '@swagger-api/apidom-ns-{spec}-{version}';
import * as adapter from '../src/adapter.ts';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const yamlSpec = fs
.readFileSync(path.join(__dirname, 'fixtures', 'sample-spec.yaml'))
.toString();
const jsonSpec = fs
.readFileSync(path.join(__dirname, 'fixtures', 'sample-spec.json'))
.toString();
describe('adapter', function () {
context('given definition in YAML 1.2 format', function () {
specify('should detect proper media type', async function () {
assert.isTrue(await adapter.detect(yamlSpec));
});
specify('should detect minor version bump', async function () {
assert.isTrue(await adapter.detect('{specField}: {majorVersion}.{minorPlusOne}.0'));
});
specify('should detect patch version bump', async function () {
assert.isTrue(await adapter.detect('{specField}: {majorVersion}.{minorVersion}.1'));
});
specify('should not detect major version bump', async function () {
assert.isFalse(await adapter.detect('{specField}: {majorPlusOne}.0.0'));
});
});
context('given definition in JSON format', function () {
specify('should detect proper media type', async function () {
assert.isTrue(await adapter.detect(jsonSpec));
});
});
it('should parse', async function () {
const parseResult = await adapter.parse(yamlSpec, { sourceMap: true });
assert.isTrue(isParseResultElement(parseResult));
assert.isTrue(is{RootElement}(parseResult.api));
expect(sexprs(parseResult)).toMatchSnapshot();
});
context('given zero byte empty file', function () {
specify('should return empty parse result', async function () {
const parseResult = await adapter.parse('', { sourceMap: true });
assert.isTrue(parseResult.isEmpty);
});
});
context('detectionRegExp', function () {
specify('should reject invalid version ranges', function () {
assert.isFalse(adapter.detectionRegExp.test('{specField}: {majorVersion}.01.0'));
assert.isFalse(adapter.detectionRegExp.test('{specField}: {majorVersion}.0.x'));
});
specify('should detect version ranges in forward compatible way', function () {
assert.isTrue(adapter.detectionRegExp.test('{specField}: {majorVersion}.{minorVersion}.0'));
assert.isTrue(adapter.detectionRegExp.test('{specField}: {majorVersion}.{minorVersion}.145'));
assert.isTrue(adapter.detectionRegExp.test('{specField}: {majorVersion}.{minorPlusOne}.0'));
});
});
});
Add the new parser adapters to the apidom-reference package. Based on OpenAPI 3.2.0 implementation, a complete integration requires creating components for all four phases: parse, resolve, dereference, and bundle.
For JSON parser, create packages/apidom-reference/src/parse/parsers/{spec}-json-{version}/index.ts:
import { pick } from 'ramda';
import { ParseResultElement } from '@swagger-api/apidom-core';
import {
parse,
mediaTypes as {SpecName}{Version}MediaTypes,
detect,
} from '@swagger-api/apidom-parser-adapter-{spec}-json-{version}';
import ParserError from '../../../errors/ParserError.ts';
import Parser, { ParserOptions } from '../Parser.ts';
import File from '../../../File.ts';
export type { default as Parser, ParserOptions } from '../Parser.ts';
export type { default as File, FileOptions } from '../../../File.ts';
/**
* @public
*/
export interface {SpecName}JSON{Version}ParserOptions extends Omit<ParserOptions, 'name'> {}
/**
* Parser for {Spec Name} {Version} JSON documents.
*
* @public
*/
class {SpecName}JSON{Version}Parser extends Parser {
public syntacticAnalysis?: 'direct' | 'indirect';
public refractorOpts!: object;
constructor(options?: {SpecName}JSON{Version}ParserOptions) {
const { fileExtensions = [], mediaTypes = {SpecName}{Version}MediaTypes, ...rest } = options ?? {};
super({ ...rest, name: '{spec}-json-{version}', fileExtensions, mediaTypes });
}
async canParse(file: File): Promise<boolean> {
const hasSupportedFileExtension =
this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension);
const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType);
if (!hasSupportedFileExtension) return false;
if (hasSupportedMediaType) return true;
if (!hasSupportedMediaType) {
return detect(file.toString());
}
return false;
}
async parse(file: File): Promise<ParseResultElement> {
const source = file.toString();
try {
const parserOpts = pick(['sourceMap', 'syntacticAnalysis', 'refractorOpts'], this);
return await parse(source, parserOpts);
} catch (error: unknown) {
throw new ParserError(`Error parsing "${file.uri}"`, { cause: error });
}
}
}
export default {SpecName}JSON{Version}Parser;
Template Variables:
{SpecName}: Parser class prefix (e.g., OpenAPI, AsyncAPI, Arazzo){Version}: Version in class name (e.g., 3_2, 2019_09){spec}: Spec identifier (e.g., openapi, asyncapi){version}: Version identifier (e.g., 3-2, 2019-09){Spec Name}: Human-readable (e.g., OpenAPI)For YAML parser, create packages/apidom-reference/src/parse/parsers/{spec}-yaml-{version}/index.ts:
Same structure as JSON parser but:
{SpecName}YAML{Version}Parser@swagger-api/apidom-parser-adapter-{spec}-yaml-{version}'{spec}-yaml-{version}'For JSON parser, create packages/apidom-reference/test/parse/parsers/{spec}-json-{version}/index.ts:
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { assert } from 'chai';
import { mediaTypes, is{RootElement} } from '@swagger-api/apidom-ns-{spec}-{version}';
import {SpecName}JSON{Version}Parser from '../../../../src/parse/parsers/{spec}-json-{version}/index.ts';
import File from '../../../../src/File.ts';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
describe('parsers', function () {
context('{SpecName}JSON{Version}Parser', function () {
context('canParse', function () {
context('given file with .json extension', function () {
context('and with proper media type', function () {
specify('should return true', async function () {
const file = File({
uri: '/path/to/spec.json',
mediaType: mediaTypes.latest('json'),
});
const parser = new {SpecName}JSON{Version}Parser();
assert.isTrue(await parser.canParse(file));
});
});
context('and with unknown media type', function () {
context('and file data is buffer and can be detected', function () {
specify('should return true', async function () {
const url = path.join(__dirname, 'fixtures', 'sample-spec.json');
const file = File({ uri: url, data: fs.readFileSync(url) });
const parser = new {SpecName}JSON{Version}Parser();
assert.isTrue(await parser.canParse(file));
});
});
});
});
context('given file with unknown extension', function () {
specify('should return false', async function () {
const file = File({ uri: '/path/to/spec.yaml' });
const parser = new {SpecName}JSON{Version}Parser({ fileExtensions: ['.json'] });
assert.isFalse(await parser.canParse(file));
});
});
});
context('parse', function () {
specify('should return parse result', async function () {
const url = path.join(__dirname, 'fixtures', 'sample-spec.json');
const file = File({ uri: url, data: fs.readFileSync(url) });
const parser = new {SpecName}JSON{Version}Parser();
const result = await parser.parse(file);
assert.isTrue(is{RootElement}(result.api));
});
});
});
});
Similar test structure for YAML parser.
Create fixture files in test/parse/parsers/{spec}-json-{version}/fixtures/ and test/parse/parsers/{spec}-yaml-{version}/fixtures/.
Edit packages/apidom-reference/src/configuration/saturated.ts:
Add imports at the top:
import {SpecName}JSON{Version}Parser from '../parse/parsers/{spec}-json-{version}/index.ts';
import {SpecName}YAML{Version}Parser from '../parse/parsers/{spec}-yaml-{version}/index.ts';
Add to parsers array (maintain alphabetical/version order):
options.parse.parsers = [
// ... existing parsers ...
new {SpecName}JSON{Version}Parser({ allowEmpty: true, sourceMap: false }),
new {SpecName}YAML{Version}Parser({ allowEmpty: true, sourceMap: false }),
// ... remaining parsers ...
];
Placement guidelines:
Edit packages/apidom-reference/package.json:
Add the new parser adapter packages to dependencies:
{
"dependencies": {
"@swagger-api/apidom-parser-adapter-{spec}-json-{version}": "^1.2.2",
"@swagger-api/apidom-parser-adapter-{spec}-yaml-{version}": "^1.2.2"
}
}
Based on OpenAPI 3.2.0 implementation patterns, create strategies for resolve, dereference, and bundle components:
Create packages/apidom-reference/src/resolve/strategies/{spec}-{version}/index.ts:
import {SpecName}{Version}DereferenceStrategy from '../../dereference/strategies/{spec}-{version}/index.ts';
/**
* Resolve strategy for {Spec Name} {Version}.
* Delegates to dereference strategy for resolution.
*
* @public
*/
class {SpecName}{Version}ResolveStrategy extends {SpecName}{Version}DereferenceStrategy {
constructor(options?: {SpecName}{Version}DereferenceStrategyOptions) {
super(options);
this.name = '{spec}-{version}';
}
}
export default {SpecName}{Version}ResolveStrategy;
Create packages/apidom-reference/src/dereference/strategies/{spec}-{version}/index.ts:
import { ParseResultElement, isObjectElement } from '@swagger-api/apidom-core';
import {
is{RootElement},
keyMap as {specName}{Version}KeyMap,
getNodeType,
} from '@swagger-api/apidom-ns-{spec}-{version}';
import { visit, mergeAllVisitors } from '@swagger-api/apidom-reference/configuration/visitors';
import {SpecName}{Version}DereferenceVisitor from './visitors/{spec}-{version}/index.ts';
import type { DereferenceStrategy } from '../../DereferenceStrategy.ts';
/**
* Dereference strategy for {Spec Name} {Version}.
*
* @public
*/
class {SpecName}{Version}DereferenceStrategy implements DereferenceStrategy {
public name = '{spec}-{version}-dereference';
canDereference(element: ParseResultElement): boolean {
if (!isParseResultElement(element)) return false;
const { api } = element;
return is{RootElement}(api);
}
async dereference(
file: File,
parseResult: ParseResultElement,
options: ReferenceOptions
): Promise<Element> {
const visitors = mergeAllVisitors([
{SpecName}{Version}DereferenceVisitor({ ...options, keyMap: {specName}{Version}KeyMap, getNodeType }),
]);
return visit(parseResult.api, visitors, options);
}
}
export default {SpecName}{Version}DereferenceStrategy;
Create packages/apidom-reference/src/dereference/strategies/{spec}-{version}/visitors/{spec}-{version}/index.ts:
import { Mixin } from 'ts-mixer';
import { Element } from '@swagger-api/apidom-core';
import ReferenceVisitor from '../../../visitors/ReferenceVisitor.ts';
import SpecificationVisitor from '../../../visitors/SpecificationVisitor.ts';
import {SpecName}{Version}Visitor from './visitors/index.ts';
/**
* Main visitor for dereferencing {Spec Name} {Version} documents.
* Combines Reference resolution, Specification traversal, and spec-specific logic.
*
* @public
*/
class Visitor extends Mixin(ReferenceVisitor, SpecificationVisitor, {SpecName}{Version}Visitor) {
constructor(options) {
super(options);
this.element = new Element();
}
}
export default Visitor;
Create packages/apidom-reference/src/dereference/strategies/{spec}-{version}/selectors/$anchor.ts:
/**
* URI selector for $anchor in {Spec Name} {Version}.
* Handles anchor-based references within the document.
*/
export const $AnchorSelector = (element: Element): Element | undefined => {
// Implementation for finding elements by $anchor
// ...
};
Create packages/apidom-reference/src/dereference/strategies/{spec}-{version}/selectors/uri.ts:
/**
* URI selector for absolute and relative URIs in {Spec Name} {Version}.
* Handles standard $ref resolution.
*/
export const uriSelector = (element: Element): Element | undefined => {
// Implementation for finding elements by URI
// ...
};
Create packages/apidom-reference/src/bundle/strategies/{spec}-{version}/index.ts:
import {SpecName}{Version}DereferenceStrategy from '../../dereference/strategies/{spec}-{version}/index.ts';
/**
* Bundle strategy for {Spec Name} {Version}.
* Creates a compound document from multi-file {Spec Name} resources.
*
* @public
*/
class {SpecName}{Version}BundleStrategy extends {SpecName}{Version}DereferenceStrategy {
constructor(options?: {SpecName}{Version}DereferenceStrategyOptions) {
super(options);
this.name = '{spec}-{version}-bundle';
}
// Override behavior for bundling instead of dereferencing
// Bundling inlines external references while preserving structure
}
export default {SpecName}{Version}BundleStrategy;
Update packages/apidom-reference/src/configuration/saturated.ts:
// Import strategies
import {SpecName}{Version}ResolveStrategy from '../resolve/strategies/{spec}-{version}/index.ts';
import {SpecName}{Version}DereferenceStrategy from '../dereference/strategies/{spec}-{version}/index.ts';
import {SpecName}{Version}BundleStrategy from '../bundle/strategies/{spec}-{version}/index.ts';
// In the configuration function:
// Add to resolve strategies
options.resolve.strategies = [
new {SpecName}{Version}ResolveStrategy(),
// ... other strategies
];
// Add to dereference strategies
options.dereference.strategies = [
new {SpecName}{Version}DereferenceStrategy(),
// ... other strategies
];
// Add to bundle strategies
options.bundle.strategies = [
new {SpecName}{Version}BundleStrategy(),
// ... other strategies
];
Create comprehensive tests in packages/apidom-reference/test/:
test/resolve/strategies/{spec}-{version}/ - Test external reference resolutiontest/dereference/strategies/{spec}-{version}/ - Test $ref transcludingtest/bundle/strategies/{spec}-{version}/ - Test multi-file bundlingExample dereference test structure:
describe('dereference', function () {
context('strategies', function () {
context('{SpecName}{Version}DereferenceStrategy', function () {
specify('should dereference internal $refs', async function () {
const spec = `
{
"{specField}": "{version}",
"components": {
"schemas": {
"Pet": { "type": "object" }
}
},
"paths": {
"/pets": {
"get": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Pet" }
}
}
}
}
}
}
}
}
`;
const dereferenced = await dereference(spec);
// Verify $ref was replaced with actual schema
assert.isTrue(isPetSchema(dereferenced.paths['/pets'].get.responses['200'].content['application/json'].schema));
});
});
});
});
Important Note: The reference strategy implementation (Phase 7) is optional for basic parser adapter functionality but highly recommended for production use. OpenAPI 3.2.0 included complete reference integration from the start. If skipping initially, create a follow-up task to add these strategies.
Build the new parser adapter packages:
# Build JSON adapter
cd packages/apidom-parser-adapter-{spec}-json-{version}
npm run build
# Build YAML adapter
cd ../apidom-parser-adapter-{spec}-yaml-{version}
npm run build
Run tests for parser adapters:
cd packages/apidom-parser-adapter-{spec}-json-{version}
npm run test
npm run typescript:check-types
npm run lint
cd ../apidom-parser-adapter-{spec}-yaml-{version}
npm run test
npm run typescript:check-types
npm run lint
Build apidom-reference:
cd ../../apidom-reference
npm run build
Run apidom-reference tests:
npm run test
npm run typescript:check-types
npm run lint
Run full monorepo build (from root):
npm run build
npm run test
Update parser adapter README files:
Update apidom-reference README:
Create CHANGELOG.md for new packages:
# Changelog
All notable changes to this project will be documented in this file.
## [1.0.0] - YYYY-MM-DD
### Added
- Initial release
- Parser adapter for {Spec Name} {Version} JSON documents
- Detection support for {specField} field
- Full refraction into semantic ApiDOM
Parser Adapter Packages:
apidom-parser-adapter-{spec}-{format}-{version}apidom-parser-adapter-openapi-json-3-2apidom-parser-adapter-asyncapi-yaml-3apidom-parser-adapter-json-schema-json-2019-09apidom-parser-adapter-arazzo-yaml-1Parser Classes in apidom-reference:
{SpecName}{Format}{Version}Parser3_2, 2019_09OpenAPIJSON3_2ParserAsyncAPIYAML3ParserJSONSchema2019_09ParserArazzoJSON1ParserDirectory Names:
{spec}-{format}-{version}openapi-json-3-2, asyncapi-yaml-3JSON Detection RegExp:
/"{specField}"\s*:\s*"(?<version_json>{versionPattern})"/
YAML Detection RegExp (supports both YAML and JSON):
/(?<YAML>^(["']?){specField}\2\s*:\s*(["']?)(?<version_yaml>{versionPattern})\3(?:\s+|$))|(?<JSON>"{specField}"\s*:\s*"(?<version_json>{versionPattern})")/m
Version Patterns:
3\.2\.(?:[1-9]\d*|0) (3.2.x)3\.(?:[1-9]\d*|0)\.(?:[1-9]\d*|0) (3.x.x)(?:[1-9]\d*|0)\.(?:[1-9]\d*|0)\.(?:[1-9]\d*|0) (x.x.x)JSON Schema:
apidom-ns-json-schema-{version}apidom-parser-adapter-json-schema-{format}-{version}apidom-parser-adapter-json-schema-json-2019-09apidom-parser-adapter-json-schema-yaml-2020-12API Design Systems:
apidom-parser-adapter-api-design-systems-{format}In saturated.ts, maintain this order:
Import and use media types from the namespace package:
import { mediaTypes, {SpecName}MediaTypes } from '@swagger-api/apidom-ns-{spec}-{version}';
// For JSON
const jsonMediaTypes = new {SpecName}MediaTypes(
...mediaTypes.filterByFormat('generic'),
...mediaTypes.filterByFormat('json'),
);
// For YAML
const yamlMediaTypes = new {SpecName}MediaTypes(
...mediaTypes.filterByFormat('generic'),
...mediaTypes.filterByFormat('yaml'),
);
Import resolution failures:
.ts extensionTypeScript errors:
npm run typescript:check-types to identify issuesSnapshot mismatches:
npm run test:update-snapshots to updateDetection failures:
Parse errors:
Parser not found in apidom-reference:
saturated.tsoptions.parse.parsers arrayMedia type detection fails:
Existing Parser Adapters:
apidom-parser-adapter-arazzo-json-1apidom-parser-adapter-openapi-json-3-1apidom-parser-adapter-asyncapi-yaml-3apidom-reference Integration:
packages/apidom-reference/src/parse/parsers/packages/apidom-reference/src/configuration/saturated.tspackages/apidom-reference/test/parse/parsers/Architecture: See CLAUDE.md "Parser Adapters" section
After running this skill, verify: