@voxgig/apidef 2.0.0 → 2.1.0
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/apidef.js +14 -20
- package/dist/apidef.js.map +1 -1
- package/dist/builder/flow/flowHeuristic01.js +37 -9
- package/dist/builder/flow/flowHeuristic01.js.map +1 -1
- package/dist/builder/flow.js +3 -3
- package/dist/builder/flow.js.map +1 -1
- package/dist/guide/heuristic01.js +197 -117
- package/dist/guide/heuristic01.js.map +1 -1
- package/dist/guide.js +37 -11
- package/dist/guide.js.map +1 -1
- package/dist/parse.d.ts +2 -1
- package/dist/parse.js +81 -3
- package/dist/parse.js.map +1 -1
- package/dist/transform/clean.d.ts +3 -0
- package/dist/transform/clean.js +16 -0
- package/dist/transform/clean.js.map +1 -0
- package/dist/transform/entity.js +21 -2
- package/dist/transform/entity.js.map +1 -1
- package/dist/transform/field.js +24 -1
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/operation.js +131 -47
- package/dist/transform/operation.js.map +1 -1
- package/dist/transform.d.ts +1 -8
- package/dist/transform.js +121 -95
- package/dist/transform.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.js +3 -2
- package/dist/types.js.map +1 -1
- package/dist/utility.d.ts +7 -1
- package/dist/utility.js +85 -32
- package/dist/utility.js.map +1 -1
- package/model/apidef.jsonic +1 -0
- package/package.json +8 -9
- package/src/apidef.ts +23 -33
- package/src/builder/flow/flowHeuristic01.ts +44 -9
- package/src/builder/flow.ts +4 -3
- package/src/guide/heuristic01.ts +281 -124
- package/src/guide.ts +49 -14
- package/src/parse.ts +106 -4
- package/src/transform/clean.ts +28 -0
- package/src/transform/entity.ts +26 -3
- package/src/transform/field.ts +27 -1
- package/src/transform/operation.ts +203 -64
- package/src/transform.ts +29 -23
- package/src/types.ts +3 -2
- package/src/utility.ts +113 -1
- package/src/builder/flow/flowHeuristic01.ts~ +0 -45
package/src/parse.ts
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { bundleFromString, createConfig } from '@redocly/openapi-core'
|
|
4
4
|
|
|
5
|
+
import { each, snakify } from 'jostraca'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import { depluralize } from './utility'
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
// Parse an API definition source into a JSON sructure.
|
|
@@ -16,12 +20,51 @@ async function parse(kind: string, source: any, meta?: any) {
|
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
async function parseOpenAPI(source: any, meta?: any) {
|
|
19
|
-
const
|
|
20
|
-
|
|
23
|
+
const base = meta?.config || {}
|
|
24
|
+
const config: any = await createConfig(base)
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
// First pass: parse without dereferencing to preserve $refs
|
|
27
|
+
const bundleWithRefs = await bundleFromString({
|
|
23
28
|
source,
|
|
24
29
|
config,
|
|
30
|
+
dereference: false,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Walk the tree and add x-ref properties
|
|
34
|
+
const seen = new WeakSet()
|
|
35
|
+
let refCount = 0
|
|
36
|
+
|
|
37
|
+
function addXRefs(obj: any, path: string = '') {
|
|
38
|
+
if (!obj || typeof obj !== 'object' || seen.has(obj)) return
|
|
39
|
+
seen.add(obj)
|
|
40
|
+
|
|
41
|
+
if (Array.isArray(obj)) {
|
|
42
|
+
obj.forEach((item, index) => addXRefs(item, `${path}[${index}]`))
|
|
43
|
+
} else {
|
|
44
|
+
// Check for $ref property
|
|
45
|
+
if (obj.$ref && typeof obj.$ref === 'string') {
|
|
46
|
+
obj['x-ref'] = obj.$ref
|
|
47
|
+
refCount++
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Recursively process all properties
|
|
51
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
52
|
+
if (value && typeof value === 'object') {
|
|
53
|
+
addXRefs(value, path ? `${path}.${key}` : key)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
addXRefs(bundleWithRefs.bundle.parsed)
|
|
60
|
+
|
|
61
|
+
// Serialize back to string with x-refs preserved
|
|
62
|
+
const sourceWithXRefs = JSON.stringify(bundleWithRefs.bundle.parsed)
|
|
63
|
+
|
|
64
|
+
// Second pass: parse with dereferencing
|
|
65
|
+
const bundle = await bundleFromString({
|
|
66
|
+
source: sourceWithXRefs,
|
|
67
|
+
config,
|
|
25
68
|
dereference: true,
|
|
26
69
|
})
|
|
27
70
|
|
|
@@ -31,6 +74,65 @@ async function parseOpenAPI(source: any, meta?: any) {
|
|
|
31
74
|
}
|
|
32
75
|
|
|
33
76
|
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
// Make consistent changes to support semantic entities.
|
|
83
|
+
function rewrite(def: any) {
|
|
84
|
+
const paths = def.paths
|
|
85
|
+
each(paths, (path) => {
|
|
86
|
+
each(path.parameters, (param: any) => {
|
|
87
|
+
|
|
88
|
+
let new_param = param.name
|
|
89
|
+
let new_path = path.key$
|
|
90
|
+
|
|
91
|
+
// Rename param if nane is "id", and not the final param.
|
|
92
|
+
// Rewrite /foo/{id}/bar as /foo/{foo_id}/bar.
|
|
93
|
+
// Avoids ambiguity with bar id.
|
|
94
|
+
if (param.name.match(/^id$/i)) {
|
|
95
|
+
let m = path.key$.match(/\/([^\/]+)\/{id\}\/[^\/]/)
|
|
96
|
+
|
|
97
|
+
if (m) {
|
|
98
|
+
const parent = depluralize(snakify(m[1]))
|
|
99
|
+
new_param = `${parent}_id`
|
|
100
|
+
new_path = path.key$.replace('{id}', '{' + new_param + '}')
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
new_param = depluralize(snakify(param.name))
|
|
105
|
+
new_path = path.key$.replace('{' + param.name + '}', '{' + new_param + '}')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let pathdef = paths[path.key$]
|
|
109
|
+
delete paths[path.key$]
|
|
110
|
+
|
|
111
|
+
paths[new_path] = pathdef
|
|
112
|
+
path.key$ = new_path
|
|
113
|
+
|
|
114
|
+
param.name = new_param
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
sortkeys(def, 'paths')
|
|
120
|
+
sortkeys(def, 'components')
|
|
121
|
+
|
|
122
|
+
return def
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
function sortkeys(obj: any, prop: string) {
|
|
127
|
+
const sorted: any = {}
|
|
128
|
+
const sorted_keys = Object.keys(obj[prop]).sort()
|
|
129
|
+
for (let sk of sorted_keys) {
|
|
130
|
+
sorted[sk] = obj[prop][sk]
|
|
131
|
+
}
|
|
132
|
+
obj[prop] = sorted
|
|
133
|
+
}
|
|
134
|
+
|
|
34
135
|
export {
|
|
35
|
-
parse
|
|
136
|
+
parse,
|
|
137
|
+
rewrite,
|
|
36
138
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
import { each, getx } from 'jostraca'
|
|
3
|
+
|
|
4
|
+
import type { TransformResult } from '../transform'
|
|
5
|
+
|
|
6
|
+
import { walk } from '@voxgig/struct'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const cleanTransform = async function(
|
|
11
|
+
ctx: any,
|
|
12
|
+
): Promise<TransformResult> {
|
|
13
|
+
const { apimodel } = ctx
|
|
14
|
+
|
|
15
|
+
walk(apimodel, (k: any, v: any) => {
|
|
16
|
+
if ('string' === typeof k && k.includes('$')) {
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
19
|
+
return v
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return { ok: true, msg: 'clean' }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
cleanTransform
|
|
28
|
+
}
|
package/src/transform/entity.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
import { each } from 'jostraca'
|
|
3
|
+
import { each, snakify } from 'jostraca'
|
|
4
4
|
|
|
5
5
|
import type { TransformResult, Transform } from '../transform'
|
|
6
6
|
|
|
7
7
|
import { fixName } from '../transform'
|
|
8
8
|
|
|
9
|
+
import { depluralize } from '../utility'
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
const entityTransform: Transform = async function(
|
|
11
13
|
ctx: any,
|
|
@@ -30,12 +32,16 @@ const entityTransform: Transform = async function(
|
|
|
30
32
|
id: {
|
|
31
33
|
name: 'id',
|
|
32
34
|
field: 'id',
|
|
33
|
-
}
|
|
35
|
+
},
|
|
36
|
+
ancestors: []
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
fixName(entityModel, guideEntity.key$)
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
let ancestors: string[] = []
|
|
42
|
+
let ancestorsDone = false
|
|
43
|
+
|
|
44
|
+
each(guideEntity.path, (guidePath: any, pathStr: string) => {
|
|
39
45
|
const path = guidePath.key$
|
|
40
46
|
const pathdef = def.paths[path]
|
|
41
47
|
|
|
@@ -50,8 +56,25 @@ const entityTransform: Transform = async function(
|
|
|
50
56
|
.filter((p: string) => p.startsWith('{'))
|
|
51
57
|
.map((p: string) => p.substring(1, p.length - 1))
|
|
52
58
|
|
|
59
|
+
if (!ancestorsDone) {
|
|
60
|
+
// Find all path sections matching /foo/{..param..} and build ancestors array
|
|
61
|
+
const paramRegex = /\/([a-zA-Z0-9_-]+)\/\{[a-zA-Z0-9_-]+\}/g
|
|
62
|
+
let m
|
|
63
|
+
while ((m = paramRegex.exec(pathStr)) !== null) {
|
|
64
|
+
// Skip if this is the last section (the entity itself)
|
|
65
|
+
const remainingPath = pathStr.substring(m.index + m[0].length)
|
|
66
|
+
if (remainingPath.length > 0) {
|
|
67
|
+
const ancestorName = depluralize(snakify(m[1]))
|
|
68
|
+
ancestors.push(ancestorName)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ancestorsDone = true
|
|
73
|
+
}
|
|
53
74
|
})
|
|
54
75
|
|
|
76
|
+
entityModel.ancestors = ancestors
|
|
77
|
+
|
|
55
78
|
msg += guideEntity.name + ' '
|
|
56
79
|
})
|
|
57
80
|
|
package/src/transform/field.ts
CHANGED
|
@@ -69,7 +69,8 @@ function fieldbuild(
|
|
|
69
69
|
field.name = property.key$
|
|
70
70
|
fixName(field, field.name)
|
|
71
71
|
|
|
72
|
-
field.type = property.type
|
|
72
|
+
// field.type = property.type
|
|
73
|
+
resolveFieldType(entityModel, field, property)
|
|
73
74
|
fixName(field, field.type, 'type')
|
|
74
75
|
|
|
75
76
|
field.short = property.description
|
|
@@ -93,6 +94,31 @@ function fieldbuild(
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
|
|
97
|
+
// Resovles a heuristic "primary" type which subsumes the more detailed type.
|
|
98
|
+
// The primary type is only: string, number, boolean, null, object, array
|
|
99
|
+
function resolveFieldType(entity: any, field: any, property: any) {
|
|
100
|
+
const ptt = typeof property.type
|
|
101
|
+
|
|
102
|
+
if ('string' === ptt) {
|
|
103
|
+
field.type = property.type
|
|
104
|
+
}
|
|
105
|
+
else if (Array.isArray(property.type)) {
|
|
106
|
+
field.type =
|
|
107
|
+
(property.type.filter((t: string) => 'null' != t).sort()[0]) ||
|
|
108
|
+
property.type[0] ||
|
|
109
|
+
'string'
|
|
110
|
+
field.typelist = property.type
|
|
111
|
+
}
|
|
112
|
+
else if ('undefined' === ptt && null != property.enum) {
|
|
113
|
+
field.type = 'string'
|
|
114
|
+
field.enum = property.enum
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`APIDEF: Unsupported property type: ${property.type} (${entity.name}.${field.name})`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
96
122
|
|
|
97
123
|
export {
|
|
98
124
|
fieldTransform
|