@kubb/adapter-oas 5.0.0-beta.37 → 5.0.0-beta.38
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/dist/index.cjs +212 -190
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +211 -189
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +71 -28
- package/src/refs.ts +6 -3
- package/src/schemaDiagnostics.ts +22 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/adapter-oas",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.38",
|
|
4
4
|
"description": "OpenAPI and Swagger adapter for Kubb. Parses and validates OAS 2.0/3.x specifications into a @kubb/ast RootNode for downstream code generation plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adapter",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"oas": "^32.1.18",
|
|
48
48
|
"oas-normalize": "^16.0.4",
|
|
49
49
|
"swagger2openapi": "^7.0.8",
|
|
50
|
-
"@kubb/core": "5.0.0-beta.
|
|
50
|
+
"@kubb/core": "5.0.0-beta.38"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/swagger2openapi": "^7.0.4",
|
package/src/adapter.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { once } from '@internals/utils'
|
|
2
1
|
import { ast, createAdapter } from '@kubb/core'
|
|
3
2
|
import type { AdapterSource } from '@kubb/core'
|
|
4
3
|
import BaseOas from 'oas'
|
|
@@ -68,39 +67,83 @@ export const adapterOas = createAdapter<AdapterOas>((options) => {
|
|
|
68
67
|
let nameMapping = new Map<string, string>()
|
|
69
68
|
let parsedDocument: Document | null = null
|
|
70
69
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
70
|
+
// Cache per source and per document so one adapter instance reused across a `defineConfig` array
|
|
71
|
+
// parses each config's spec instead of replaying the first one. Keying the document by its source
|
|
72
|
+
// object still collapses a config's concurrent `stream()` (build) and `parse()` (studio) calls,
|
|
73
|
+
// which share one source object, onto a single parse. The document-derived caches key off the
|
|
74
|
+
// resulting document, so distinct configs (distinct documents) stay isolated.
|
|
75
|
+
const documentCache = new WeakMap<AdapterSource, Promise<Document>>()
|
|
76
|
+
const schemasCache = new WeakMap<Document, Promise<ReturnType<typeof getSchemas>['schemas']>>()
|
|
77
|
+
const baseOasCache = new WeakMap<Document, BaseOas>()
|
|
78
|
+
const schemaParserCache = new WeakMap<Document, ReturnType<typeof createSchemaParser>>()
|
|
79
|
+
const preScanCache = new WeakMap<Document, ReturnType<typeof preScan>>()
|
|
80
|
+
|
|
81
|
+
function ensureDocument(source: AdapterSource): Promise<Document> {
|
|
82
|
+
const cached = documentCache.get(source)
|
|
83
|
+
if (cached) return cached
|
|
84
|
+
|
|
85
|
+
const promise = (async () => {
|
|
86
|
+
const fresh = await parseFromConfig(source)
|
|
87
|
+
if (validate) await validateDocument(fresh)
|
|
88
|
+
parsedDocument = fresh
|
|
89
|
+
return fresh
|
|
90
|
+
})()
|
|
91
|
+
documentCache.set(source, promise)
|
|
92
|
+
return promise
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ensureSchemas(document: Document): Promise<ReturnType<typeof getSchemas>['schemas']> {
|
|
96
|
+
const cached = schemasCache.get(document)
|
|
97
|
+
if (cached) return cached
|
|
98
|
+
|
|
99
|
+
const promise = Promise.resolve().then(() => {
|
|
100
|
+
const result = getSchemas(document, { contentType })
|
|
101
|
+
nameMapping = result.nameMapping
|
|
102
|
+
return result.schemas
|
|
103
|
+
})
|
|
104
|
+
schemasCache.set(document, promise)
|
|
105
|
+
return promise
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function ensureBaseOas(document: Document): BaseOas {
|
|
109
|
+
const cached = baseOasCache.get(document)
|
|
110
|
+
if (cached) return cached
|
|
111
|
+
|
|
112
|
+
const baseOas = new BaseOas(document)
|
|
113
|
+
baseOasCache.set(document, baseOas)
|
|
114
|
+
return baseOas
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function ensureSchemaParser(document: Document): ReturnType<typeof createSchemaParser> {
|
|
118
|
+
const cached = schemaParserCache.get(document)
|
|
119
|
+
if (cached) return cached
|
|
120
|
+
|
|
121
|
+
const parser = createSchemaParser({ document, contentType })
|
|
122
|
+
schemaParserCache.set(document, parser)
|
|
123
|
+
return parser
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function ensurePreScan(
|
|
127
|
+
document: Document,
|
|
128
|
+
schemas: ReturnType<typeof getSchemas>['schemas'],
|
|
129
|
+
parseSchema: ReturnType<typeof ensureSchemaParser>['parseSchema'],
|
|
130
|
+
parseOperation: ReturnType<typeof ensureSchemaParser>['parseOperation'],
|
|
131
|
+
baseOas: BaseOas,
|
|
132
|
+
): ReturnType<typeof preScan> {
|
|
133
|
+
const cached = preScanCache.get(document)
|
|
134
|
+
if (cached) return cached
|
|
135
|
+
|
|
136
|
+
const result = preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe })
|
|
137
|
+
preScanCache.set(document, result)
|
|
138
|
+
return result
|
|
139
|
+
}
|
|
97
140
|
|
|
98
141
|
async function createStream(source: AdapterSource): Promise<ast.InputStreamNode> {
|
|
99
142
|
const document = await ensureDocument(source)
|
|
100
143
|
const schemas = await ensureSchemas(document)
|
|
101
144
|
const { parseSchema, parseOperation } = ensureSchemaParser(document)
|
|
102
145
|
const baseOas = ensureBaseOas(document)
|
|
103
|
-
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(schemas, parseSchema, parseOperation, baseOas)
|
|
146
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas)
|
|
104
147
|
|
|
105
148
|
return createInputStream({
|
|
106
149
|
schemas,
|
package/src/refs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Diagnostic, diagnosticCode, Diagnostics } from '@kubb/core'
|
|
1
|
+
import { type Diagnostic, diagnosticCode, DiagnosticError, Diagnostics } from '@kubb/core'
|
|
2
2
|
import { isReference } from './guards.ts'
|
|
3
3
|
import type { Document } from './types.ts'
|
|
4
4
|
|
|
@@ -48,8 +48,11 @@ export function resolveRef<T = unknown>(document: Document, $ref: string): T | n
|
|
|
48
48
|
location: { kind: 'schema', pointer: origRef, ref: origRef },
|
|
49
49
|
}
|
|
50
50
|
// Report the unresolved ref into the active build and resolve to null, like any
|
|
51
|
-
// other unresolvable ref. The build collects it and keeps going.
|
|
52
|
-
|
|
51
|
+
// other unresolvable ref. The build collects it and keeps going. Outside a build there is no
|
|
52
|
+
// sink, so throw rather than silently returning null.
|
|
53
|
+
if (!Diagnostics.report(diagnostic)) {
|
|
54
|
+
throw new DiagnosticError(diagnostic)
|
|
55
|
+
}
|
|
53
56
|
return null
|
|
54
57
|
}
|
|
55
58
|
|
package/src/schemaDiagnostics.ts
CHANGED
|
@@ -11,7 +11,15 @@ import { isHandledFormat } from './resolvers.ts'
|
|
|
11
11
|
* no-op outside one, and repeats are deduped by the build.
|
|
12
12
|
*/
|
|
13
13
|
export function reportSchemaDiagnostics({ node, name }: { node: ast.SchemaNode; name: string }): void {
|
|
14
|
-
visit(node, `#/components/schemas/${name}`)
|
|
14
|
+
visit(node, `#/components/schemas/${escapePointerToken(name)}`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
|
|
19
|
+
* property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
|
|
20
|
+
*/
|
|
21
|
+
function escapePointerToken(token: string): string {
|
|
22
|
+
return token.replace(/~/g, '~0').replace(/\//g, '~1')
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
function visit(node: ast.SchemaNode, pointer: string): void {
|
|
@@ -36,7 +44,7 @@ function visit(node: ast.SchemaNode, pointer: string): void {
|
|
|
36
44
|
|
|
37
45
|
if (node.type === 'object') {
|
|
38
46
|
for (const property of node.properties) {
|
|
39
|
-
visit(property.schema, `${pointer}/properties/${property.name}`)
|
|
47
|
+
visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`)
|
|
40
48
|
}
|
|
41
49
|
if (node.additionalProperties && typeof node.additionalProperties === 'object') {
|
|
42
50
|
visit(node.additionalProperties, `${pointer}/additionalProperties`)
|
|
@@ -44,16 +52,25 @@ function visit(node: ast.SchemaNode, pointer: string): void {
|
|
|
44
52
|
return
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
if (node.type === 'array'
|
|
55
|
+
if (node.type === 'array') {
|
|
48
56
|
for (const item of node.items ?? []) {
|
|
49
57
|
visit(item, `${pointer}/items`)
|
|
50
58
|
}
|
|
51
59
|
return
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
if (node.type === 'tuple') {
|
|
63
|
+
// Each tuple position has its own pointer, so index them. A shared `/items` would collapse
|
|
64
|
+
// distinct diagnostics in the dedupe.
|
|
65
|
+
for (const [index, item] of (node.items ?? []).entries()) {
|
|
66
|
+
visit(item, `${pointer}/items/${index}`)
|
|
67
|
+
}
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
if (node.type === 'union' || node.type === 'intersection') {
|
|
55
|
-
for (const member of node.members ?? []) {
|
|
56
|
-
visit(member, pointer)
|
|
72
|
+
for (const [index, member] of (node.members ?? []).entries()) {
|
|
73
|
+
visit(member, `${pointer}/members/${index}`)
|
|
57
74
|
}
|
|
58
75
|
}
|
|
59
76
|
}
|