@speclynx/apidom-reference 4.0.3 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/apidom-reference.browser.js +43 -19
  3. package/dist/apidom-reference.browser.min.js +1 -1
  4. package/package.json +27 -26
  5. package/src/File.ts +0 -63
  6. package/src/Reference.ts +0 -38
  7. package/src/ReferenceSet.ts +0 -73
  8. package/src/bundle/index.ts +0 -57
  9. package/src/bundle/strategies/BundleStrategy.ts +0 -27
  10. package/src/bundle/strategies/openapi-3-1/index.ts +0 -57
  11. package/src/configuration/empty.ts +0 -1
  12. package/src/configuration/saturated.ts +0 -72
  13. package/src/dereference/index.ts +0 -96
  14. package/src/dereference/strategies/DereferenceStrategy.ts +0 -27
  15. package/src/dereference/strategies/apidom/index.ts +0 -128
  16. package/src/dereference/strategies/apidom/selectors/element-id.ts +0 -48
  17. package/src/dereference/strategies/apidom/visitor.ts +0 -316
  18. package/src/dereference/strategies/arazzo-1/index.ts +0 -158
  19. package/src/dereference/strategies/arazzo-1/selectors/$anchor.ts +0 -9
  20. package/src/dereference/strategies/arazzo-1/selectors/uri.ts +0 -5
  21. package/src/dereference/strategies/arazzo-1/source-descriptions.ts +0 -317
  22. package/src/dereference/strategies/arazzo-1/util.ts +0 -33
  23. package/src/dereference/strategies/arazzo-1/visitor.ts +0 -574
  24. package/src/dereference/strategies/asyncapi-2/index.ts +0 -133
  25. package/src/dereference/strategies/asyncapi-2/visitor.ts +0 -589
  26. package/src/dereference/strategies/openapi-2/index.ts +0 -136
  27. package/src/dereference/strategies/openapi-2/visitor.ts +0 -745
  28. package/src/dereference/strategies/openapi-3-0/index.ts +0 -134
  29. package/src/dereference/strategies/openapi-3-0/visitor.ts +0 -760
  30. package/src/dereference/strategies/openapi-3-1/index.ts +0 -141
  31. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.ts +0 -64
  32. package/src/dereference/strategies/openapi-3-1/selectors/uri.ts +0 -54
  33. package/src/dereference/strategies/openapi-3-1/util.ts +0 -83
  34. package/src/dereference/strategies/openapi-3-1/visitor.ts +0 -1053
  35. package/src/dereference/util.ts +0 -29
  36. package/src/errors/BundleError.ts +0 -8
  37. package/src/errors/DereferenceError.ts +0 -8
  38. package/src/errors/EvaluationElementIdError.ts +0 -8
  39. package/src/errors/EvaluationJsonSchema$anchorError.ts +0 -8
  40. package/src/errors/EvaluationJsonSchemaUriError.ts +0 -8
  41. package/src/errors/InvalidJsonSchema$anchorError.ts +0 -12
  42. package/src/errors/JsonSchema$anchorError.ts +0 -8
  43. package/src/errors/JsonSchemaUriError.ts +0 -8
  44. package/src/errors/MaximumBundleDepthError.ts +0 -8
  45. package/src/errors/MaximumDereferenceDepthError.ts +0 -8
  46. package/src/errors/MaximumResolveDepthError.ts +0 -8
  47. package/src/errors/ParseError.ts +0 -8
  48. package/src/errors/ParserError.ts +0 -8
  49. package/src/errors/PluginError.ts +0 -15
  50. package/src/errors/ResolveError.ts +0 -8
  51. package/src/errors/ResolverError.ts +0 -8
  52. package/src/errors/UnmatchedBundleStrategyError.ts +0 -8
  53. package/src/errors/UnmatchedDereferenceStrategyError.ts +0 -8
  54. package/src/errors/UnmatchedParserError.ts +0 -8
  55. package/src/errors/UnmatchedResolveStrategyError.ts +0 -8
  56. package/src/errors/UnmatchedResolverError.ts +0 -8
  57. package/src/errors/UnresolvableReferenceError.ts +0 -8
  58. package/src/index.ts +0 -135
  59. package/src/options/index.ts +0 -239
  60. package/src/options/util.ts +0 -22
  61. package/src/parse/index.ts +0 -67
  62. package/src/parse/parsers/Parser.ts +0 -80
  63. package/src/parse/parsers/apidom-json/index.ts +0 -78
  64. package/src/parse/parsers/arazzo-json-1/index.ts +0 -76
  65. package/src/parse/parsers/arazzo-json-1/source-descriptions.ts +0 -280
  66. package/src/parse/parsers/arazzo-yaml-1/index.ts +0 -77
  67. package/src/parse/parsers/arazzo-yaml-1/source-descriptions.ts +0 -16
  68. package/src/parse/parsers/asyncapi-json-2/index.ts +0 -58
  69. package/src/parse/parsers/asyncapi-yaml-2/index.ts +0 -58
  70. package/src/parse/parsers/binary/index-browser.ts +0 -60
  71. package/src/parse/parsers/binary/index-node.ts +0 -57
  72. package/src/parse/parsers/json/index.ts +0 -52
  73. package/src/parse/parsers/openapi-json-2/index.ts +0 -58
  74. package/src/parse/parsers/openapi-json-3-0/index.ts +0 -59
  75. package/src/parse/parsers/openapi-json-3-1/index.ts +0 -59
  76. package/src/parse/parsers/openapi-yaml-2/index.ts +0 -58
  77. package/src/parse/parsers/openapi-yaml-3-0/index.ts +0 -59
  78. package/src/parse/parsers/openapi-yaml-3-1/index.ts +0 -59
  79. package/src/parse/parsers/yaml-1-2/index.ts +0 -60
  80. package/src/resolve/index.ts +0 -75
  81. package/src/resolve/resolvers/HTTPResolver.ts +0 -58
  82. package/src/resolve/resolvers/Resolver.ts +0 -25
  83. package/src/resolve/resolvers/file/index-browser.ts +0 -24
  84. package/src/resolve/resolvers/file/index-node.ts +0 -55
  85. package/src/resolve/resolvers/http-axios/cache/MemoryCache.ts +0 -46
  86. package/src/resolve/resolvers/http-axios/index.ts +0 -130
  87. package/src/resolve/strategies/ResolveStrategy.ts +0 -26
  88. package/src/resolve/strategies/apidom/index.ts +0 -78
  89. package/src/resolve/strategies/asyncapi-2/index.ts +0 -78
  90. package/src/resolve/strategies/openapi-2/index.ts +0 -78
  91. package/src/resolve/strategies/openapi-3-0/index.ts +0 -78
  92. package/src/resolve/strategies/openapi-3-1/index.ts +0 -78
  93. package/src/resolve/util.ts +0 -39
  94. package/src/util/plugins.ts +0 -37
  95. package/src/util/url.ts +0 -285
@@ -1,67 +0,0 @@
1
- import { isEmpty } from 'ramda';
2
- import { ParseResultElement } from '@speclynx/apidom-datamodel';
3
-
4
- import * as url from '../util/url.ts';
5
- import File from '../File.ts';
6
- import * as plugins from '../util/plugins.ts';
7
- import Parser from './parsers/Parser.ts';
8
- import ParseError from '../errors/ParseError.ts';
9
- import UnmatchedParserError from '../errors/UnmatchedParserError.ts';
10
- import { readFile } from '../resolve/util.ts';
11
- import type { ReferenceOptions } from '../options/index.ts';
12
-
13
- /**
14
- * Parses the given file's contents, using the configured parser plugins.
15
- */
16
- const parseFile = async (file: File, options: ReferenceOptions): Promise<ParseResultElement> => {
17
- const optsBoundParsers = options.parse.parsers.map((parser) => {
18
- const clonedParser = Object.create(parser);
19
- return Object.assign(clonedParser, options.parse.parserOpts);
20
- });
21
-
22
- const parsers: Parser[] = await plugins.filter('canParse', [file, options], optsBoundParsers);
23
-
24
- // we couldn't find any parser for this File
25
- if (isEmpty(parsers)) {
26
- throw new UnmatchedParserError(`Could not find a parser that can parse the file "${file.uri}"`);
27
- }
28
-
29
- try {
30
- const { plugin, result } = await plugins.run('parse', [file, options], parsers);
31
-
32
- // empty files handling
33
- if (!plugin.allowEmpty && result.isEmpty) {
34
- return Promise.reject(
35
- new ParseError(`Error while parsing file "${file.uri}": file is empty`),
36
- );
37
- }
38
-
39
- return result;
40
- } catch (error: any) {
41
- throw new ParseError(`Error while parsing file "${file.uri}"`, { cause: error });
42
- }
43
- };
44
-
45
- /**
46
- * Parses a file into ApiDOM.
47
- */
48
- const parse = async (uri: string, options: ReferenceOptions): Promise<ParseResultElement> => {
49
- /**
50
- * If the path is a filesystem path, then convert it to a URL.
51
- *
52
- * NOTE: According to the JSON Reference spec, these should already be URLs,
53
- * but, in practice, many people use local filesystem paths instead.
54
- * So we're being generous here and doing the conversion automatically.
55
- * This is not intended to be a 100% bulletproof solution.
56
- * If it doesn't work for your use-case, then use a URL instead.
57
- */
58
- const file = new File({
59
- uri: url.sanitize(url.stripHash(uri)),
60
- mediaType: options.parse.mediaType,
61
- });
62
- const data = await readFile(file, options);
63
-
64
- return parseFile(new File({ ...file, data }), options);
65
- };
66
-
67
- export default parse;
@@ -1,80 +0,0 @@
1
- import { ParseResultElement } from '@speclynx/apidom-datamodel';
2
-
3
- import File from '../../File.ts';
4
- import type { ReferenceOptions } from '../../options/index.ts';
5
-
6
- /**
7
- * @public
8
- */
9
- export interface ParserOptions {
10
- readonly name: string;
11
- readonly allowEmpty?: boolean;
12
- readonly sourceMap?: boolean;
13
- readonly style?: boolean;
14
- readonly strict?: boolean;
15
- readonly fileExtensions?: string[];
16
- readonly mediaTypes?: string[];
17
- }
18
-
19
- /**
20
- * @public
21
- */
22
- abstract class Parser {
23
- public readonly name: string;
24
-
25
- /**
26
- * Whether to allow "empty" files. This includes zero-byte files.
27
- */
28
- public allowEmpty: boolean;
29
-
30
- /**
31
- * Whether to generate source map during parsing.
32
- */
33
- public sourceMap: boolean;
34
-
35
- /**
36
- * Whether to capture format-specific style information for round-trip preservation.
37
- */
38
- public style: boolean;
39
-
40
- /**
41
- * Whether to use strict parsing (native JSON.parse/YAML instead of tree-sitter).
42
- */
43
- public strict: boolean;
44
-
45
- /**
46
- * List of supported file extensions.
47
- */
48
- public fileExtensions: string[];
49
-
50
- /**
51
- * List of supported media types.
52
- */
53
- public mediaTypes: string[];
54
-
55
- constructor({
56
- name,
57
- allowEmpty = true,
58
- sourceMap = false,
59
- style = false,
60
- strict = true,
61
- fileExtensions = [],
62
- mediaTypes = [],
63
- }: ParserOptions) {
64
- this.name = name;
65
- this.allowEmpty = allowEmpty;
66
- this.sourceMap = sourceMap;
67
- this.style = style;
68
- this.strict = strict;
69
- this.fileExtensions = fileExtensions;
70
- this.mediaTypes = mediaTypes;
71
- }
72
-
73
- abstract canParse(file: File, options?: ReferenceOptions): boolean | Promise<boolean>;
74
- abstract parse(
75
- file: File,
76
- options?: ReferenceOptions,
77
- ): ParseResultElement | Promise<ParseResultElement>;
78
- }
79
-
80
- export default Parser;
@@ -1,78 +0,0 @@
1
- import { Namespace, ParseResultElement, isParseResultElement } from '@speclynx/apidom-datamodel';
2
-
3
- import ParserError from '../../../errors/ParserError.ts';
4
- import Parser, { ParserOptions } from '../Parser.ts';
5
- import File from '../../../File.ts';
6
-
7
- export type { default as Parser, ParserOptions } from '../Parser.ts';
8
- export type { default as File, FileOptions } from '../../../File.ts';
9
-
10
- /**
11
- * @public
12
- */
13
- export interface ApiDOMJSONParserOptions extends Omit<ParserOptions, 'name'> {
14
- readonly namespace?: Namespace;
15
- }
16
-
17
- /**
18
- * @public
19
- */
20
- class ApiDOMJSONParser extends Parser {
21
- public namespace: Namespace;
22
-
23
- public ['apidom-json']!: { namespace?: Namespace };
24
-
25
- constructor(options?: ApiDOMJSONParserOptions) {
26
- const {
27
- fileExtensions = [],
28
- mediaTypes = ['application/vnd.apidom', 'application/vnd.apidom+json'],
29
- namespace = new Namespace(),
30
- ...rest
31
- } = options ?? {};
32
-
33
- super({ ...rest, name: 'apidom-json', fileExtensions, mediaTypes });
34
- this.namespace = namespace;
35
- }
36
-
37
- canParse(file: File): boolean {
38
- const hasSupportedFileExtension =
39
- this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension);
40
- const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType);
41
-
42
- if (!hasSupportedFileExtension) return false;
43
- if (hasSupportedMediaType) return true;
44
- if (!hasSupportedMediaType) {
45
- try {
46
- return this.namespace.fromRefract(JSON.parse(file.toString())) && true;
47
- } catch {
48
- return false;
49
- }
50
- }
51
- return false;
52
- }
53
-
54
- parse(file: File): ParseResultElement {
55
- const source = file.toString();
56
- const namespace = this['apidom-json']?.namespace ?? this.namespace;
57
-
58
- // allow empty files
59
- if (this.allowEmpty && source.trim() === '') {
60
- return new ParseResultElement();
61
- }
62
-
63
- try {
64
- const element = namespace.fromRefract(JSON.parse(source));
65
-
66
- if (!isParseResultElement(element)) {
67
- element.classes.push('result');
68
- return new ParseResultElement([element]);
69
- }
70
-
71
- return element;
72
- } catch (error: unknown) {
73
- throw new ParserError(`Error parsing "${file.uri}"`, { cause: error });
74
- }
75
- }
76
- }
77
-
78
- export default ApiDOMJSONParser;
@@ -1,76 +0,0 @@
1
- import { pick } from 'ramda';
2
- import { ParseResultElement } from '@speclynx/apidom-datamodel';
3
- import {
4
- parse,
5
- mediaTypes as ArazzoJSON1MediaTypes,
6
- detect,
7
- } from '@speclynx/apidom-parser-adapter-arazzo-json-1';
8
-
9
- import ParserError from '../../../errors/ParserError.ts';
10
- import Parser, { ParserOptions } from '../Parser.ts';
11
- import File from '../../../File.ts';
12
- import type { ReferenceOptions } from '../../../options/index.ts';
13
- import { parseSourceDescriptions } from './source-descriptions.ts';
14
- export type { default as Parser, ParserOptions } from '../Parser.ts';
15
- export type { default as File, FileOptions } from '../../../File.ts';
16
-
17
- /**
18
- * @public
19
- */
20
- export interface ArazzoJSON1ParserOptions extends Omit<ParserOptions, 'name'> {}
21
-
22
- /**
23
- * @public
24
- */
25
- class ArazzoJSON1Parser extends Parser {
26
- public refractorOpts!: object;
27
-
28
- constructor(options?: ArazzoJSON1ParserOptions) {
29
- const { fileExtensions = [], mediaTypes = ArazzoJSON1MediaTypes, ...rest } = options ?? {};
30
-
31
- super({ ...rest, name: 'arazzo-json-1', fileExtensions, mediaTypes });
32
- }
33
-
34
- async canParse(file: File): Promise<boolean> {
35
- const hasSupportedFileExtension =
36
- this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension);
37
- const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType);
38
-
39
- if (!hasSupportedFileExtension) return false;
40
- if (hasSupportedMediaType) return true;
41
- if (!hasSupportedMediaType) {
42
- return detect(file.toString());
43
- }
44
- return false;
45
- }
46
-
47
- async parse(file: File, options?: ReferenceOptions): Promise<ParseResultElement> {
48
- const source = file.toString();
49
-
50
- try {
51
- const parserOpts = pick(['sourceMap', 'style', 'strict', 'refractorOpts'], this);
52
- const parseResult = await parse(source, parserOpts);
53
-
54
- const shouldParseSourceDescriptions =
55
- options?.parse?.parserOpts?.[this.name]?.sourceDescriptions ??
56
- options?.parse?.parserOpts?.sourceDescriptions;
57
- if (shouldParseSourceDescriptions) {
58
- const sourceDescriptions = await parseSourceDescriptions(
59
- parseResult,
60
- file.uri,
61
- options!,
62
- this.name,
63
- );
64
- parseResult.push(...sourceDescriptions);
65
- }
66
-
67
- return parseResult;
68
- } catch (error: unknown) {
69
- throw new ParserError(`Error parsing "${file.uri}"`, { cause: error });
70
- }
71
- }
72
- }
73
-
74
- export { parseSourceDescriptions } from './source-descriptions.ts';
75
-
76
- export default ArazzoJSON1Parser;
@@ -1,280 +0,0 @@
1
- import {
2
- Element,
3
- ParseResultElement,
4
- AnnotationElement,
5
- isArrayElement,
6
- isStringElement,
7
- } from '@speclynx/apidom-datamodel';
8
- import {
9
- isArazzoSpecification1Element,
10
- isSourceDescriptionElement,
11
- } from '@speclynx/apidom-ns-arazzo-1';
12
- import { isSwaggerElement } from '@speclynx/apidom-ns-openapi-2';
13
- import { isOpenApi3_0Element } from '@speclynx/apidom-ns-openapi-3-0';
14
- import { isOpenApi3_1Element } from '@speclynx/apidom-ns-openapi-3-1';
15
- import { toValue } from '@speclynx/apidom-core';
16
-
17
- import File from '../../../File.ts';
18
- import * as url from '../../../util/url.ts';
19
- import type { ReferenceOptions } from '../../../options/index.ts';
20
- import { merge as mergeOptions } from '../../../options/util.ts';
21
- import parse from '../../index.ts';
22
-
23
- // shared key for recursion state (works across JSON/YAML parsers)
24
- const ARAZZO_RECURSION_KEY = 'arazzo-1';
25
-
26
- interface ParseSourceDescriptionContext {
27
- baseURI: string;
28
- options: ReferenceOptions;
29
- currentDepth: number;
30
- visitedUrls: Set<string>;
31
- }
32
-
33
- /**
34
- * Parses a single source description element.
35
- * Returns ParseResultElement on success, or undefined if skipped.
36
- */
37
- async function parseSourceDescription(
38
- sourceDescription: Element,
39
- ctx: ParseSourceDescriptionContext,
40
- ): Promise<ParseResultElement> {
41
- const parseResult = new ParseResultElement();
42
-
43
- if (!isSourceDescriptionElement(sourceDescription)) {
44
- const annotation = new AnnotationElement(
45
- 'Element is not a valid SourceDescriptionElement. Skipping',
46
- );
47
- annotation.classes.push('warning');
48
- parseResult.push(annotation);
49
- return parseResult;
50
- }
51
-
52
- // set class and metadata from source description element
53
- parseResult.classes.push('source-description');
54
- if (isStringElement(sourceDescription.name))
55
- parseResult.setMetaProperty('name', toValue(sourceDescription.name) as string);
56
- if (isStringElement(sourceDescription.type))
57
- parseResult.setMetaProperty('type', toValue(sourceDescription.type) as string);
58
-
59
- const sourceDescriptionURI = toValue(sourceDescription.url);
60
- if (typeof sourceDescriptionURI !== 'string') {
61
- const annotation = new AnnotationElement(
62
- 'Source description URL is missing or not a string. Skipping',
63
- );
64
- annotation.classes.push('warning');
65
- parseResult.push(annotation);
66
- return parseResult;
67
- }
68
-
69
- // normalize URI for consistent cycle detection and cache key matching
70
- const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
71
- parseResult.setMetaProperty('retrievalURI', retrievalURI);
72
-
73
- // skip if already visited (cycle detection)
74
- if (ctx.visitedUrls.has(retrievalURI)) {
75
- const annotation = new AnnotationElement(
76
- `Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`,
77
- );
78
- annotation.classes.push('warning');
79
- parseResult.push(annotation);
80
- return parseResult;
81
- }
82
- ctx.visitedUrls.add(retrievalURI);
83
-
84
- try {
85
- const sourceDescriptionParseResult = await parse(
86
- retrievalURI,
87
- mergeOptions(ctx.options, {
88
- parse: {
89
- mediaType: 'text/plain', // allow parser plugin detection
90
- parserOpts: {
91
- // nested documents should parse all their source descriptions
92
- // (parent's name filter doesn't apply to nested documents)
93
- sourceDescriptions: true,
94
- [ARAZZO_RECURSION_KEY]: {
95
- sourceDescriptionsDepth: ctx.currentDepth + 1,
96
- sourceDescriptionsVisitedUrls: ctx.visitedUrls,
97
- },
98
- },
99
- },
100
- }),
101
- );
102
- // merge parsed result into our parse result
103
- for (const item of sourceDescriptionParseResult) {
104
- parseResult.push(item);
105
- }
106
- } catch (error: unknown) {
107
- // create error annotation instead of failing entire parse
108
- const message = error instanceof Error ? error.message : String(error);
109
- const annotation = new AnnotationElement(
110
- `Error parsing source description "${retrievalURI}": ${message}`,
111
- );
112
- annotation.classes.push('error');
113
- parseResult.push(annotation);
114
- return parseResult;
115
- }
116
-
117
- // only allow OpenAPI and Arazzo as source descriptions
118
- const { api: sourceDescriptionAPI } = parseResult;
119
- const isOpenApi =
120
- isSwaggerElement(sourceDescriptionAPI) ||
121
- isOpenApi3_0Element(sourceDescriptionAPI) ||
122
- isOpenApi3_1Element(sourceDescriptionAPI);
123
- const isArazzo = isArazzoSpecification1Element(sourceDescriptionAPI);
124
-
125
- if (!isOpenApi && !isArazzo) {
126
- const annotation = new AnnotationElement(
127
- `Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`,
128
- );
129
- annotation.classes.push('warning');
130
- parseResult.push(annotation);
131
- return parseResult;
132
- }
133
-
134
- // validate declared type matches actual parsed type
135
- const declaredType = toValue(sourceDescription.type);
136
- if (typeof declaredType === 'string') {
137
- if (declaredType === 'openapi' && !isOpenApi) {
138
- const annotation = new AnnotationElement(
139
- `Source description "${retrievalURI}" declared as "openapi" but parsed as Arazzo document`,
140
- );
141
- annotation.classes.push('warning');
142
- parseResult.push(annotation);
143
- } else if (declaredType === 'arazzo' && !isArazzo) {
144
- const annotation = new AnnotationElement(
145
- `Source description "${retrievalURI}" declared as "arazzo" but parsed as OpenAPI document`,
146
- );
147
- annotation.classes.push('warning');
148
- parseResult.push(annotation);
149
- }
150
- }
151
-
152
- return parseResult;
153
- }
154
-
155
- /**
156
- * Parses source descriptions from an Arazzo document's ParseResult.
157
- *
158
- * Each source description result is attached to its corresponding
159
- * SourceDescriptionElement's meta as 'parseResult' for easy access,
160
- * regardless of success or failure. On failure, the ParseResultElement
161
- * contains annotations explaining what went wrong.
162
- *
163
- * @param parseResult - ParseResult containing an Arazzo specification
164
- * @param parseResultRetrievalURI - URI from which the parseResult was retrieved
165
- * @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
166
- * in `parse.parserOpts` to filter which source descriptions to process.
167
- * @param parserName - Parser name for options lookup (defaults to 'arazzo-json-1')
168
- * @returns Array of ParseResultElements. Returns one ParseResultElement per source description
169
- * (each with class 'source-description' and metadata: name, type, retrievalURI).
170
- * May return early with a single-element array containing a warning annotation when:
171
- * - The API is not an Arazzo specification
172
- * - The sourceDescriptions field is missing or not an array
173
- * - Maximum parse depth is exceeded (error annotation)
174
- * Returns an empty array when no source description names match the filter.
175
- *
176
- * @example
177
- * ```typescript
178
- * import { toValue } from '@speclynx/apidom-core';
179
- * import { options, mergeOptions } from '@speclynx/apidom-reference';
180
- * import { parseSourceDescriptions } from '@speclynx/apidom-reference/parse/parsers/arazzo-json-1';
181
- *
182
- * // Parse all source descriptions
183
- * const results = await parseSourceDescriptions(parseResult, uri, options);
184
- *
185
- * // Filter by name
186
- * const filtered = await parseSourceDescriptions(parseResult, uri, mergeOptions(options, {
187
- * parse: { parserOpts: { sourceDescriptions: ['petStore'] } }
188
- * }));
189
- *
190
- * // Access parsed document from source description element
191
- * const sourceDesc = parseResult.api.sourceDescriptions.get(0);
192
- * const parsedDoc = sourceDesc.meta.get('parseResult');
193
- * const retrievalURI = toValue(parsedDoc.meta.get('retrievalURI'));
194
- * ```
195
- *
196
- * @public
197
- */
198
- export async function parseSourceDescriptions(
199
- parseResult: ParseResultElement,
200
- parseResultRetrievalURI: string,
201
- options: ReferenceOptions,
202
- parserName: string = 'arazzo-json-1',
203
- ): Promise<ParseResultElement[]> {
204
- const { api } = parseResult;
205
- const file = new File({ uri: url.sanitize(url.stripHash(parseResultRetrievalURI)) });
206
- const results: ParseResultElement[] = [];
207
-
208
- /**
209
- * Validate prerequisites for parsing source descriptions.
210
- * Return warning annotations if validation fails.
211
- */
212
- if (!isArazzoSpecification1Element(api)) {
213
- const annotation = new AnnotationElement(
214
- 'Cannot parse source descriptions: API is not an Arazzo specification',
215
- );
216
- annotation.classes.push('warning');
217
- return [new ParseResultElement([annotation])];
218
- }
219
- if (!isArrayElement(api.sourceDescriptions)) {
220
- const annotation = new AnnotationElement(
221
- 'Cannot parse source descriptions: sourceDescriptions field is missing or not an array',
222
- );
223
- annotation.classes.push('warning');
224
- return [new ParseResultElement([annotation])];
225
- }
226
-
227
- // user config: parser-specific options take precedence over global parserOpts
228
- const maxDepth =
229
- options?.parse?.parserOpts?.[parserName]?.sourceDescriptionsMaxDepth ??
230
- options?.parse?.parserOpts?.sourceDescriptionsMaxDepth ??
231
- +Infinity;
232
-
233
- // recursion state comes from shared key (works across JSON/YAML)
234
- const sharedOpts = options?.parse?.parserOpts?.[ARAZZO_RECURSION_KEY] ?? {};
235
- const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
236
- const visitedUrls: Set<string> = sharedOpts.sourceDescriptionsVisitedUrls ?? new Set();
237
-
238
- // add current file to visited URLs to prevent cycles
239
- visitedUrls.add(file.uri);
240
-
241
- if (currentDepth >= maxDepth) {
242
- const annotation = new AnnotationElement(
243
- `Maximum parse depth of ${maxDepth} has been exceeded by file "${file.uri}"`,
244
- );
245
- annotation.classes.push('error');
246
- const parseResult = new ParseResultElement([annotation]);
247
- parseResult.classes.push('source-description');
248
- return [parseResult];
249
- }
250
-
251
- const ctx: ParseSourceDescriptionContext = {
252
- baseURI: file.uri,
253
- options,
254
- currentDepth,
255
- visitedUrls,
256
- };
257
-
258
- // determine which source descriptions to parse (array filters by name)
259
- const sourceDescriptionsOption =
260
- options?.parse?.parserOpts?.[parserName]?.sourceDescriptions ??
261
- options?.parse?.parserOpts?.sourceDescriptions;
262
-
263
- const sourceDescriptions = Array.isArray(sourceDescriptionsOption)
264
- ? api.sourceDescriptions.filter((sd) => {
265
- if (!isSourceDescriptionElement(sd)) return false;
266
- const name = toValue(sd.name);
267
- return typeof name === 'string' && sourceDescriptionsOption.includes(name);
268
- })
269
- : api.sourceDescriptions;
270
-
271
- // process sequentially to ensure proper cycle detection with shared visitedUrls
272
- for (const sourceDescription of sourceDescriptions) {
273
- const sourceDescriptionParseResult = await parseSourceDescription(sourceDescription, ctx);
274
- // always attach result (even on failure - contains annotations)
275
- sourceDescription.meta.set('parseResult', sourceDescriptionParseResult);
276
- results.push(sourceDescriptionParseResult);
277
- }
278
-
279
- return results;
280
- }
@@ -1,77 +0,0 @@
1
- import { pick } from 'ramda';
2
- import { ParseResultElement } from '@speclynx/apidom-datamodel';
3
- import {
4
- parse,
5
- mediaTypes as ArazzoYAML1MediaTypes,
6
- detect,
7
- } from '@speclynx/apidom-parser-adapter-arazzo-yaml-1';
8
-
9
- import ParserError from '../../../errors/ParserError.ts';
10
- import Parser, { ParserOptions } from '../Parser.ts';
11
- import File from '../../../File.ts';
12
- import type { ReferenceOptions } from '../../../options/index.ts';
13
- import { parseSourceDescriptions } from './source-descriptions.ts';
14
-
15
- export type { default as Parser, ParserOptions } from '../Parser.ts';
16
- export type { default as File, FileOptions } from '../../../File.ts';
17
-
18
- /**
19
- * @public
20
- */
21
- export interface ArazzoYAML1ParserOptions extends Omit<ParserOptions, 'name'> {}
22
-
23
- /**
24
- * @public
25
- */
26
- class ArazzoYAML1Parser extends Parser {
27
- public refractorOpts!: object;
28
-
29
- constructor(options?: ArazzoYAML1ParserOptions) {
30
- const { fileExtensions = [], mediaTypes = ArazzoYAML1MediaTypes, ...rest } = options ?? {};
31
-
32
- super({ ...rest, name: 'arazzo-yaml-1', fileExtensions, mediaTypes });
33
- }
34
-
35
- async canParse(file: File): Promise<boolean> {
36
- const hasSupportedFileExtension =
37
- this.fileExtensions.length === 0 ? true : this.fileExtensions.includes(file.extension);
38
- const hasSupportedMediaType = this.mediaTypes.includes(file.mediaType);
39
-
40
- if (!hasSupportedFileExtension) return false;
41
- if (hasSupportedMediaType) return true;
42
- if (!hasSupportedMediaType) {
43
- return detect(file.toString());
44
- }
45
- return false;
46
- }
47
-
48
- async parse(file: File, options?: ReferenceOptions): Promise<ParseResultElement> {
49
- const source = file.toString();
50
-
51
- try {
52
- const parserOpts = pick(['sourceMap', 'style', 'strict', 'refractorOpts'], this);
53
- const parseResult = await parse(source, parserOpts);
54
-
55
- const shouldParseSourceDescriptions =
56
- options?.parse?.parserOpts?.[this.name]?.sourceDescriptions ??
57
- options?.parse?.parserOpts?.sourceDescriptions;
58
- if (shouldParseSourceDescriptions) {
59
- const sourceDescriptions = await parseSourceDescriptions(
60
- parseResult,
61
- file.uri,
62
- options!,
63
- this.name,
64
- );
65
- parseResult.push(...sourceDescriptions);
66
- }
67
-
68
- return parseResult;
69
- } catch (error: unknown) {
70
- throw new ParserError(`Error parsing "${file.uri}"`, { cause: error });
71
- }
72
- }
73
- }
74
-
75
- export { parseSourceDescriptions } from './source-descriptions.ts';
76
-
77
- export default ArazzoYAML1Parser;
@@ -1,16 +0,0 @@
1
- import { ParseResultElement } from '@speclynx/apidom-datamodel';
2
-
3
- import { parseSourceDescriptions as parseSourceDescriptionsBase } from '../arazzo-json-1/source-descriptions.ts';
4
- import type { ReferenceOptions } from '../../../options/index.ts';
5
-
6
- /**
7
- * @public
8
- */
9
- export const parseSourceDescriptions = (
10
- parseResult: ParseResultElement,
11
- parseResultRetrievalURI: string,
12
- options: ReferenceOptions,
13
- parserName: string = 'arazzo-yaml-1',
14
- ): Promise<ParseResultElement[]> => {
15
- return parseSourceDescriptionsBase(parseResult, parseResultRetrievalURI, options, parserName);
16
- };