@kubb/adapter-oas 5.0.0-alpha.9 → 5.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/factory.ts ADDED
@@ -0,0 +1,165 @@
1
+ import path from 'node:path'
2
+ import { mergeDeep, URLPath } from '@internals/utils'
3
+ import type { AdapterSource } from '@kubb/core'
4
+ import { bundle, loadConfig } from '@redocly/openapi-core'
5
+ import OASNormalize from 'oas-normalize'
6
+ import swagger2openapi from 'swagger2openapi'
7
+ import { MERGE_DEFAULT_TITLE, MERGE_DEFAULT_VERSION, MERGE_OPENAPI_VERSION } from './constants.ts'
8
+ import { isOpenApiV2Document } from './guards.ts'
9
+ import type { Document } from './types.ts'
10
+
11
+ export type ParseOptions = {
12
+ canBundle?: boolean
13
+ enablePaths?: boolean
14
+ }
15
+
16
+ export type ValidateDocumentOptions = {
17
+ throwOnError?: boolean
18
+ }
19
+
20
+ /**
21
+ * Loads and dereferences an OpenAPI document, returning the raw `Document`.
22
+ *
23
+ * Accepts a file path string or an already-parsed document object. File paths are bundled via
24
+ * Redocly to resolve external `$ref`s. Swagger 2.0 documents are automatically up-converted
25
+ * to OpenAPI 3.0 via `swagger2openapi`.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const document = await parseDocument('./openapi.yaml')
30
+ * const document = await parse(rawDocumentObject, { canBundle: false })
31
+ * ```
32
+ */
33
+ export async function parseDocument(pathOrApi: string | Document, { canBundle = true, enablePaths = true }: ParseOptions = {}): Promise<Document> {
34
+ if (typeof pathOrApi === 'string' && canBundle) {
35
+ const config = await loadConfig()
36
+ const bundleResults = await bundle({
37
+ ref: pathOrApi,
38
+ config,
39
+ base: pathOrApi,
40
+ })
41
+
42
+ return parseDocument(bundleResults.bundle.parsed as string, {
43
+ canBundle,
44
+ enablePaths,
45
+ })
46
+ }
47
+
48
+ const oasNormalize = new OASNormalize(pathOrApi, {
49
+ enablePaths,
50
+ colorizeErrors: true,
51
+ })
52
+ const document = (await oasNormalize.load()) as Document
53
+
54
+ if (isOpenApiV2Document(document)) {
55
+ const { openapi } = await swagger2openapi.convertObj(document, {
56
+ anchors: true,
57
+ })
58
+
59
+ return openapi as Document
60
+ }
61
+
62
+ return document
63
+ }
64
+
65
+ /**
66
+ * Deep-merges multiple OpenAPI documents into a single `Document`.
67
+ *
68
+ * Each document is parsed independently then recursively merged via `mergeDeep` from `@internals/utils`.
69
+ * Throws when the input array is empty.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const document = await mergeDocuments(['./pets.yaml', './orders.yaml'])
74
+ * ```
75
+ */
76
+ export async function mergeDocuments(pathOrApi: Array<string | Document>): Promise<Document> {
77
+ const documents: Document[] = []
78
+ for (const p of pathOrApi) {
79
+ documents.push(await parseDocument(p, { enablePaths: false, canBundle: false }))
80
+ }
81
+
82
+ if (documents.length === 0) {
83
+ throw new Error('No OAS documents provided for merging.')
84
+ }
85
+
86
+ const seed: Document = {
87
+ openapi: MERGE_OPENAPI_VERSION,
88
+ info: { title: MERGE_DEFAULT_TITLE, version: MERGE_DEFAULT_VERSION },
89
+ paths: {},
90
+ components: { schemas: {} },
91
+ } as Document
92
+
93
+ const merged = documents.reduce(
94
+ (acc, current) => mergeDeep(acc as Record<string, unknown>, current as Record<string, unknown>),
95
+ seed as Record<string, unknown>,
96
+ )
97
+
98
+ return parseDocument(merged as Document)
99
+ }
100
+
101
+ /**
102
+ * Creates a `Document` from an `AdapterSource`.
103
+ *
104
+ * Handles all three source types:
105
+ * - `{ type: 'path' }` — resolves and bundles a local file path or remote URL.
106
+ * - `{ type: 'paths' }` — merges multiple file paths into a single document.
107
+ * - `{ type: 'data' }` — parses an inline string (YAML/JSON) or raw object.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const document = await parseFromConfig({ type: 'path', path: './openapi.yaml' })
112
+ * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
113
+ * ```
114
+ */
115
+ export function parseFromConfig(source: AdapterSource): Promise<Document> {
116
+ if (source.type === 'data') {
117
+ if (typeof source.data === 'object') {
118
+ return parseDocument(structuredClone(source.data) as Document)
119
+ }
120
+
121
+ return parseDocument(source.data as string, { canBundle: false })
122
+ }
123
+
124
+ if (source.type === 'paths') {
125
+ return mergeDocuments(source.paths)
126
+ }
127
+
128
+ // type === 'path'
129
+ if (new URLPath(source.path).isURL) {
130
+ return parseDocument(source.path)
131
+ }
132
+
133
+ return parseDocument(path.resolve(path.dirname(source.path), source.path))
134
+ }
135
+
136
+ /**
137
+ * Validates an OpenAPI document using `oas-normalize` with colorized error output.
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * await validateDocument(document)
142
+ * ```
143
+ */
144
+ export async function validateDocument(document: Document, { throwOnError = false }: ValidateDocumentOptions = {}): Promise<void> {
145
+ try {
146
+ const oasNormalize = new OASNormalize(document, {
147
+ enablePaths: true,
148
+ colorizeErrors: true,
149
+ })
150
+
151
+ await oasNormalize.validate({
152
+ parser: {
153
+ validate: {
154
+ errors: { colorize: true },
155
+ },
156
+ },
157
+ })
158
+ } catch (error) {
159
+ if (throwOnError) {
160
+ throw error
161
+ }
162
+
163
+ // Validation failures are non-fatal — mirror plugin-oas behavior
164
+ }
165
+ }
package/src/guards.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { isPlainObject } from '@internals/utils'
2
+ import type { OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'
3
+ import type { DiscriminatorObject, SchemaObject } from './types.ts'
4
+
5
+ /**
6
+ * Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * if (isOpenApiV2Document(doc)) {
11
+ * // doc is OpenAPIV2.Document
12
+ * }
13
+ * ```
14
+ */
15
+ export function isOpenApiV2Document(doc: unknown): doc is OpenAPIV2.Document {
16
+ return !!doc && isPlainObject(doc) && !('openapi' in doc)
17
+ }
18
+
19
+ /**
20
+ * Returns `true` when a schema should be treated as nullable.
21
+ *
22
+ * Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
23
+ * `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * isNullable({ type: 'string', nullable: true }) // true
28
+ * isNullable({ type: ['string', 'null'] }) // true
29
+ * isNullable({ type: 'string' }) // false
30
+ * ```
31
+ */
32
+ export function isNullable(schema?: SchemaObject & { 'x-nullable'?: boolean }): boolean {
33
+ const explicitNullable = schema?.nullable ?? schema?.['x-nullable']
34
+ if (explicitNullable === true) return true
35
+
36
+ const schemaType = schema?.type
37
+ if (schemaType === 'null') return true
38
+ if (Array.isArray(schemaType)) return schemaType.includes('null')
39
+
40
+ return false
41
+ }
42
+
43
+ /**
44
+ * Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * isReference({ $ref: '#/components/schemas/Pet' }) // true
49
+ * isReference({ type: 'string' }) // false
50
+ * ```
51
+ */
52
+ export function isReference(obj?: unknown): obj is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject {
53
+ return !!obj && typeof obj === 'object' && '$ref' in obj
54
+ }
55
+
56
+ /**
57
+ * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
62
+ * isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
63
+ * ```
64
+ */
65
+ export function isDiscriminator(obj?: unknown): obj is SchemaObject & { discriminator: DiscriminatorObject } {
66
+ const record = obj as Record<string, unknown>
67
+ return !!obj && !!record['discriminator'] && typeof record['discriminator'] !== 'string'
68
+ }
package/src/index.ts CHANGED
@@ -1 +1,18 @@
1
1
  export { adapterOas, adapterOasName } from './adapter.ts'
2
+ export type { ParseOptions, ValidateDocumentOptions } from './factory.ts'
3
+ export { mergeDocuments, parseDocument, parseFromConfig, validateDocument } from './factory.ts'
4
+ export type {
5
+ AdapterOas,
6
+ AdapterOasOptions,
7
+ AdapterOasResolvedOptions,
8
+ ContentType,
9
+ DiscriminatorObject,
10
+ Document,
11
+ HttpMethod,
12
+ MediaTypeObject,
13
+ Operation,
14
+ ReferenceObject,
15
+ ResponseObject,
16
+ SchemaObject,
17
+ } from './types.ts'
18
+ export { HttpMethods } from './types.ts'