@voxgig/apidef 2.4.1 → 3.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.d.ts +7 -2
- package/dist/apidef.js +190 -114
- package/dist/apidef.js.map +1 -1
- package/dist/builder/entity/entity.d.ts +3 -0
- package/dist/builder/entity/{apiEntity.js → entity.js} +12 -9
- package/dist/builder/entity/entity.js.map +1 -0
- package/dist/builder/entity/info.d.ts +3 -0
- package/dist/builder/entity/info.js +22 -0
- package/dist/builder/entity/info.js.map +1 -0
- package/dist/builder/entity.js +7 -21
- package/dist/builder/entity.js.map +1 -1
- package/dist/builder/flow/flowHeuristic01.js +21 -11
- package/dist/builder/flow/flowHeuristic01.js.map +1 -1
- package/dist/builder/flow.d.ts +2 -1
- package/dist/builder/flow.js +39 -12
- package/dist/builder/flow.js.map +1 -1
- package/dist/def.d.ts +62 -0
- package/dist/def.js +4 -0
- package/dist/def.js.map +1 -0
- package/dist/desc.d.ts +87 -0
- package/dist/desc.js +4 -0
- package/dist/desc.js.map +1 -0
- package/dist/guide/guide.d.ts +2 -1
- package/dist/guide/guide.js +161 -30
- package/dist/guide/guide.js.map +1 -1
- package/dist/guide/heuristic01.d.ts +2 -1
- package/dist/guide/heuristic01.js +1122 -234
- package/dist/guide/heuristic01.js.map +1 -1
- package/dist/model.d.ts +90 -0
- package/dist/model.js +4 -0
- package/dist/model.js.map +1 -0
- package/dist/parse.d.ts +4 -3
- package/dist/parse.js +40 -46
- package/dist/parse.js.map +1 -1
- package/dist/resolver.js +2 -1
- package/dist/resolver.js.map +1 -1
- package/dist/transform/args.d.ts +3 -0
- package/dist/transform/args.js +54 -0
- package/dist/transform/args.js.map +1 -0
- package/dist/transform/clean.d.ts +2 -2
- package/dist/transform/clean.js +28 -3
- package/dist/transform/clean.js.map +1 -1
- package/dist/transform/entity.d.ts +9 -1
- package/dist/transform/entity.js +57 -41
- package/dist/transform/entity.js.map +1 -1
- package/dist/transform/field.d.ts +1 -1
- package/dist/transform/field.js +89 -65
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/flow.d.ts +3 -0
- package/dist/transform/flow.js +26 -0
- package/dist/transform/flow.js.map +1 -0
- package/dist/transform/flowstep.d.ts +3 -0
- package/dist/transform/flowstep.js +145 -0
- package/dist/transform/flowstep.js.map +1 -0
- package/dist/transform/operation.d.ts +3 -3
- package/dist/transform/operation.js +101 -296
- package/dist/transform/operation.js.map +1 -1
- package/dist/transform/select.d.ts +3 -0
- package/dist/transform/select.js +65 -0
- package/dist/transform/select.js.map +1 -0
- package/dist/transform/top.js +24 -2
- package/dist/transform/top.js.map +1 -1
- package/dist/transform.d.ts +1 -1
- package/dist/transform.js +4 -0
- package/dist/transform.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +115 -14
- package/dist/types.js +4 -2
- package/dist/types.js.map +1 -1
- package/dist/utility.d.ts +34 -2
- package/dist/utility.js +444 -6
- package/dist/utility.js.map +1 -1
- package/model/apidef.jsonic +76 -1
- package/model/guide.jsonic +14 -44
- package/package.json +19 -16
- package/src/apidef.ts +258 -122
- package/src/builder/entity/{apiEntity.ts → entity.ts} +18 -11
- package/src/builder/entity/info.ts +53 -0
- package/src/builder/entity.ts +9 -35
- package/src/builder/flow/flowHeuristic01.ts +46 -12
- package/src/builder/flow.ts +54 -13
- package/src/def.ts +91 -0
- package/src/desc.ts +154 -0
- package/src/guide/guide.ts +208 -134
- package/src/guide/heuristic01.ts +1653 -272
- package/src/model.ts +143 -0
- package/src/parse.ts +50 -59
- package/src/resolver.ts +3 -1
- package/src/schematron.ts.off +317 -0
- package/src/transform/args.ts +98 -0
- package/src/transform/clean.ts +45 -11
- package/src/transform/entity.ts +96 -50
- package/src/transform/field.ts +136 -75
- package/src/transform/flow.ts +59 -0
- package/src/transform/flowstep.ts +242 -0
- package/src/transform/operation.ts +119 -419
- package/src/transform/select.ts +119 -0
- package/src/transform/top.ts +46 -4
- package/src/transform.ts +8 -4
- package/src/types.ts +181 -5
- package/src/utility.ts +567 -9
- package/dist/builder/entity/apiEntity.d.ts +0 -3
- package/dist/builder/entity/apiEntity.js.map +0 -1
- package/dist/builder/entity/def.d.ts +0 -3
- package/dist/builder/entity/def.js +0 -19
- package/dist/builder/entity/def.js.map +0 -1
- package/src/builder/entity/def.ts +0 -44
- package/src/guide.ts.off +0 -136
package/src/guide/heuristic01.ts
CHANGED
|
@@ -1,103 +1,285 @@
|
|
|
1
1
|
|
|
2
|
+
import { Ordu } from 'ordu'
|
|
3
|
+
import type { TaskSpec } from 'ordu'
|
|
2
4
|
|
|
3
|
-
import { each, snakify, names } from 'jostraca'
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import { each } from 'jostraca'
|
|
7
|
+
|
|
8
|
+
import { size, merge, getelem, isempty, items, keysof } from '@voxgig/struct'
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
ApiDefContext,
|
|
13
|
+
|
|
14
|
+
Guide,
|
|
15
|
+
GuideMetrics,
|
|
16
|
+
GuideEntity,
|
|
17
|
+
GuidePath,
|
|
18
|
+
GuidePathAction,
|
|
19
|
+
GuideRenameParam,
|
|
20
|
+
GuidePathOp,
|
|
21
|
+
} from '../types'
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
CmpDesc,
|
|
25
|
+
MethodDesc,
|
|
26
|
+
MethodEntityDesc,
|
|
27
|
+
EntityDesc,
|
|
28
|
+
EntityPathDesc,
|
|
29
|
+
} from '../desc'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
import type {
|
|
34
|
+
PathDef,
|
|
35
|
+
MethodDef,
|
|
36
|
+
} from '../def'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
import {
|
|
40
|
+
canonize,
|
|
11
41
|
capture,
|
|
42
|
+
debugpath,
|
|
12
43
|
find,
|
|
44
|
+
findPathsWithPrefix,
|
|
45
|
+
formatJSONIC,
|
|
46
|
+
getdlog,
|
|
47
|
+
pathMatch,
|
|
48
|
+
warnOnError,
|
|
49
|
+
} from '../utility'
|
|
50
|
+
|
|
51
|
+
import type {
|
|
52
|
+
PathMatch
|
|
13
53
|
} from '../utility'
|
|
14
54
|
|
|
15
55
|
|
|
16
|
-
type EntityDesc = {
|
|
17
|
-
name: string
|
|
18
|
-
origname: string
|
|
19
|
-
why_name?: string[]
|
|
20
|
-
plural: string
|
|
21
|
-
path: Record<string, EntityPathDesc>
|
|
22
|
-
alias: Record<string, string>
|
|
23
|
-
}
|
|
24
56
|
|
|
57
|
+
// Log non - fatal wierdness.
|
|
58
|
+
const dlog = getdlog('apidef', __filename)
|
|
59
|
+
|
|
60
|
+
// Schema components that occur less than this rate(over total method count) qualify
|
|
61
|
+
// as unique entities, not shared schemas
|
|
62
|
+
const IS_ENTCMP_METHOD_RATE = 0.21
|
|
63
|
+
const IS_ENTCMP_PATH_RATE = 0.41
|
|
25
64
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
const METHOD_IDOP: Record<string, string> = {
|
|
68
|
+
GET: 'load',
|
|
69
|
+
POST: 'create',
|
|
70
|
+
PUT: 'update',
|
|
71
|
+
DELETE: 'remove',
|
|
72
|
+
PATCH: 'patch',
|
|
73
|
+
HEAD: 'head',
|
|
74
|
+
OPTIONS: 'OPTIONS',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const METHOD_CONSIDER_ORDER: Record<string, number> = {
|
|
78
|
+
'GET': 100,
|
|
79
|
+
'POST': 200,
|
|
80
|
+
'PUT': 300,
|
|
81
|
+
'PATCH': 400,
|
|
82
|
+
'DELETE': 500,
|
|
83
|
+
'HEAD': 600,
|
|
84
|
+
'OPTIONS': 700,
|
|
29
85
|
}
|
|
30
86
|
|
|
31
|
-
// Log non-fatal wierdness.
|
|
32
|
-
const dlog = getdlog('apidef', __filename)
|
|
33
87
|
|
|
34
88
|
|
|
35
|
-
async function heuristic01(ctx:
|
|
36
|
-
let guide = ctx.model.main.api.guide
|
|
89
|
+
async function heuristic01(ctx: ApiDefContext): Promise<Guide> {
|
|
37
90
|
|
|
38
|
-
const
|
|
39
|
-
|
|
91
|
+
const analysis = new Ordu({ select: { sort: true } }).add([
|
|
92
|
+
Prepare,
|
|
93
|
+
{
|
|
94
|
+
select: 'def.paths', apply: [
|
|
95
|
+
MeasurePath,
|
|
96
|
+
{ select: '', apply: MeasureMethod },
|
|
97
|
+
PreparePath
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{ select: selectCmpXrefs, apply: MeasureRef },
|
|
101
|
+
{
|
|
102
|
+
select: selectAllMethods, apply: [
|
|
103
|
+
ResolveEntityComponent,
|
|
104
|
+
ResolveEntityName,
|
|
105
|
+
RenameParams,
|
|
106
|
+
FindActions,
|
|
107
|
+
ResolveOperation,
|
|
108
|
+
ResolveTransform,
|
|
109
|
+
// ShowNode,
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
{ select: 'work.entmap', apply: BuildEntity }
|
|
113
|
+
])
|
|
40
114
|
|
|
41
|
-
const
|
|
115
|
+
const result = analysis.execSync(ctx, {})
|
|
42
116
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
entity: entityDescs,
|
|
117
|
+
if (result.err) {
|
|
118
|
+
throw result.err
|
|
46
119
|
}
|
|
47
120
|
|
|
121
|
+
const guide = result.data.guide
|
|
122
|
+
|
|
123
|
+
// console.log('WORK', result.data.work)
|
|
124
|
+
|
|
125
|
+
// console.log('GUIDE')
|
|
126
|
+
// console.dir(guide, { depth: null })
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
// TODO: move to Ordu
|
|
130
|
+
// warnOnError('reviewEntityDescs', ctx.warn, () => reviewEntityDescs(ctx, result))
|
|
131
|
+
|
|
48
132
|
return guide
|
|
133
|
+
|
|
49
134
|
}
|
|
50
135
|
|
|
51
136
|
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
schema: ({} as Record<string, number>)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
137
|
+
function ShowNode(spec: TaskSpec) {
|
|
138
|
+
console.log('NODE', spec.node.key, spec.node.val)
|
|
139
|
+
}
|
|
58
140
|
|
|
59
|
-
let xrefs = find(ctx.def, 'x-ref')
|
|
60
|
-
// console.log('XREFS', xrefs)
|
|
61
141
|
|
|
62
|
-
|
|
142
|
+
function Prepare(spec: TaskSpec) {
|
|
143
|
+
const guide: Guide = {
|
|
144
|
+
control: {},
|
|
145
|
+
entity: {},
|
|
146
|
+
metrics: {
|
|
147
|
+
count: {
|
|
148
|
+
path: 0,
|
|
149
|
+
method: 0,
|
|
150
|
+
tag: 0,
|
|
151
|
+
cmp: 0,
|
|
152
|
+
entity: 0,
|
|
153
|
+
origcmprefs: {},
|
|
154
|
+
},
|
|
155
|
+
found: {
|
|
156
|
+
tag: {},
|
|
157
|
+
cmp: {},
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
}
|
|
63
161
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
162
|
+
Object.assign(spec.data, {
|
|
163
|
+
def: spec.ctx.def,
|
|
164
|
+
guide,
|
|
165
|
+
work: {
|
|
166
|
+
pathmap: {},
|
|
167
|
+
entmap: {},
|
|
168
|
+
entity: {
|
|
169
|
+
count: {
|
|
170
|
+
seen: 0,
|
|
171
|
+
unresolved: 0,
|
|
172
|
+
}
|
|
173
|
+
},
|
|
69
174
|
}
|
|
70
175
|
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// Expects to run over paths
|
|
180
|
+
function MeasurePath(spec: TaskSpec) {
|
|
181
|
+
const guide = spec.data.guide
|
|
182
|
+
const metrics = guide.metrics
|
|
183
|
+
// const pathstr = spec.node.key
|
|
184
|
+
const pathdef = spec.node.val
|
|
71
185
|
|
|
186
|
+
metrics.count.path++
|
|
72
187
|
|
|
73
|
-
|
|
188
|
+
metrics.count.method += (
|
|
189
|
+
(pathdef.get ? 1 : 0) +
|
|
190
|
+
(pathdef.post ? 1 : 0) +
|
|
191
|
+
(pathdef.put ? 1 : 0) +
|
|
192
|
+
(pathdef.patch ? 1 : 0) +
|
|
193
|
+
(pathdef.delete ? 1 : 0) +
|
|
194
|
+
(pathdef.head ? 1 : 0) +
|
|
195
|
+
(pathdef.options ? 1 : 0)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
// Expects to run over paths.<method>
|
|
202
|
+
function MeasureMethod(spec: TaskSpec) {
|
|
203
|
+
const guide = spec.data.guide
|
|
204
|
+
const metrics = guide.metrics
|
|
205
|
+
// const methodstr = spec.node.key
|
|
206
|
+
const methoddef = spec.node.val
|
|
207
|
+
|
|
208
|
+
const pathtags = methoddef.tags
|
|
209
|
+
if (Array.isArray(pathtags)) {
|
|
210
|
+
for (let tag of pathtags) {
|
|
211
|
+
if ('string' === typeof tag && 0 < tag.length) {
|
|
212
|
+
if (!metrics.found.tag[tag]) {
|
|
213
|
+
metrics.count.tag++
|
|
214
|
+
metrics.found.tag[tag] = {
|
|
215
|
+
name: tag,
|
|
216
|
+
canon: canonize(tag),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
74
222
|
}
|
|
75
223
|
|
|
76
224
|
|
|
225
|
+
function PreparePath(spec: TaskSpec) {
|
|
226
|
+
const work = spec.data.work
|
|
227
|
+
const pathstr = spec.node.key
|
|
228
|
+
const pathdef = spec.node.val
|
|
77
229
|
|
|
230
|
+
const pathdesc = {
|
|
231
|
+
path: pathstr,
|
|
232
|
+
def: pathdef,
|
|
233
|
+
parts: pathstr.split('/').filter((p: string) => '' != p),
|
|
234
|
+
op: {}
|
|
235
|
+
}
|
|
78
236
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
237
|
+
work.pathmap[pathstr] = pathdesc
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
function selectCmpXrefs(_source: any, spec: TaskSpec) {
|
|
242
|
+
const out = find(spec.ctx.def, 'x-ref')
|
|
243
|
+
.filter(xref => xref.val.match(/\/(components\/schemas|definitions)\//))
|
|
244
|
+
|
|
245
|
+
// console.log('selectCmpXrefs', out)
|
|
246
|
+
return out
|
|
85
247
|
}
|
|
86
248
|
|
|
87
249
|
|
|
88
|
-
function
|
|
89
|
-
const
|
|
90
|
-
const
|
|
250
|
+
function MeasureRef(spec: TaskSpec) {
|
|
251
|
+
const guide = spec.data.guide
|
|
252
|
+
const metrics = guide.metrics
|
|
253
|
+
|
|
254
|
+
let m = spec.node.val.val.match(/\/(components\/schemas|definitions)\/(.+)$/)
|
|
255
|
+
if (m) {
|
|
256
|
+
const name = canonize(m[2])
|
|
257
|
+
if (null == metrics.count.origcmprefs[name]) {
|
|
258
|
+
metrics.count.cmp++
|
|
259
|
+
metrics.count.origcmprefs[name] = 0
|
|
260
|
+
}
|
|
261
|
+
metrics.count.origcmprefs[name]++
|
|
262
|
+
|
|
263
|
+
if (null == metrics.found.cmp[name]) {
|
|
264
|
+
metrics.found.cmp[name] = { orig: m[2] }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
91
269
|
|
|
270
|
+
function selectAllMethods(_source: any, spec: TaskSpec): MethodDesc[] {
|
|
271
|
+
const ctx = spec.ctx
|
|
272
|
+
// const paths = ctx.def.paths
|
|
92
273
|
|
|
93
|
-
|
|
274
|
+
let caught = capture(ctx.def, {
|
|
94
275
|
paths:
|
|
95
|
-
['`$SELECT`',
|
|
96
|
-
['`$SELECT`',
|
|
276
|
+
['`$SELECT`', /.*/,
|
|
277
|
+
['`$SELECT`', /^get|post|put|patch|delete$/i,
|
|
97
278
|
['`$APPEND`', 'methods', {
|
|
98
279
|
path: '`select$=key.paths`',
|
|
99
|
-
method: { '`$
|
|
280
|
+
method: { '`$UPPER`': '`$KEY`' },
|
|
100
281
|
summary: '`.summary`',
|
|
282
|
+
tags: '`.tags`',
|
|
101
283
|
parameters: '`.parameters`',
|
|
102
284
|
responses: '`.responses`',
|
|
103
285
|
requestBody: '`.requestBody`'
|
|
@@ -106,347 +288,1546 @@ function resolveEntityDescs(ctx: any) {
|
|
|
106
288
|
]
|
|
107
289
|
})
|
|
108
290
|
|
|
291
|
+
// TODO: capture should return these empty objects
|
|
292
|
+
caught = caught ?? {}
|
|
293
|
+
caught.methods = caught.methods ?? []
|
|
109
294
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
295
|
+
caught.methods.sort((a: any, b: any) => {
|
|
296
|
+
if (a.path < b.path) {
|
|
297
|
+
return -1
|
|
298
|
+
}
|
|
299
|
+
else if (a.path > b.path) {
|
|
300
|
+
return 1
|
|
301
|
+
}
|
|
302
|
+
else if (METHOD_CONSIDER_ORDER[a.method] < METHOD_CONSIDER_ORDER[b.method]) {
|
|
303
|
+
return -1
|
|
304
|
+
}
|
|
305
|
+
else if (METHOD_CONSIDER_ORDER[a.method] > METHOD_CONSIDER_ORDER[b.method]) {
|
|
306
|
+
return 1
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
return 0
|
|
310
|
+
}
|
|
311
|
+
})
|
|
114
312
|
|
|
115
|
-
|
|
116
|
-
let why_op: string[] = []
|
|
313
|
+
// console.log(caught.methods.map((n: any) => n.path + ' ' + n.method))
|
|
117
314
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
315
|
+
return caught.methods || []
|
|
316
|
+
}
|
|
121
317
|
|
|
122
|
-
const why_ent: string[] = []
|
|
123
|
-
const entdesc =
|
|
124
|
-
resolveEntity(entityDescs, pathStr, methodDef, methodStr, why_ent)
|
|
125
318
|
|
|
319
|
+
function ResolveEntityComponent(spec: TaskSpec) {
|
|
320
|
+
const guide = spec.data.guide
|
|
321
|
+
const metrics = guide.metrics
|
|
126
322
|
|
|
127
|
-
|
|
128
|
-
console.log(
|
|
129
|
-
'WARNING: unable to resolve entity for method ' + methodStr +
|
|
130
|
-
' path ' + pathStr)
|
|
131
|
-
return
|
|
132
|
-
}
|
|
323
|
+
const work = spec.data.work
|
|
133
324
|
|
|
134
|
-
|
|
325
|
+
const methodDef = spec.node.val
|
|
326
|
+
const methodName = methodDef.method
|
|
327
|
+
const pathStr = methodDef.path
|
|
135
328
|
|
|
329
|
+
const parts = work.pathmap[pathStr].parts
|
|
136
330
|
|
|
137
|
-
|
|
138
|
-
// console.log('ENTRES', pathStr, methodStr)
|
|
139
|
-
// console.dir(ent2, { depth: null })
|
|
140
|
-
// }
|
|
331
|
+
let why_cmp: string[] = []
|
|
141
332
|
|
|
142
|
-
|
|
333
|
+
let responses = methodDef.responses
|
|
143
334
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
' path ' + pathStr)
|
|
148
|
-
return
|
|
149
|
-
}
|
|
335
|
+
let origxrefs: any[] = findPotentialSchemaRefs(pathStr, methodName, responses).map(val => ({
|
|
336
|
+
val
|
|
337
|
+
}))
|
|
150
338
|
|
|
339
|
+
let cmpxrefs = origxrefs
|
|
340
|
+
.filter(xref => xref.val.includes('schema') || xref.val.includes('definitions'))
|
|
341
|
+
.map(xref => {
|
|
342
|
+
let m = xref.val.match(/\/components\/schemas\/(.+)$/)
|
|
343
|
+
if (!m) {
|
|
344
|
+
m = xref.val.match(/\/definitions\/(.+)$/)
|
|
345
|
+
}
|
|
346
|
+
if (m) {
|
|
347
|
+
const cmp = canonize(m[1])
|
|
151
348
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
349
|
+
xref.cmp = cmp
|
|
350
|
+
xref.origcmp = m[1]
|
|
351
|
+
xref.origcmpref = cmp
|
|
352
|
+
}
|
|
353
|
+
return xref
|
|
354
|
+
})
|
|
355
|
+
.filter(xref => null != xref.cmp)
|
|
356
|
+
|
|
357
|
+
// TODO: identify non - ent schemas
|
|
358
|
+
.filter(xref => !xref.val.includes('Meta'))
|
|
359
|
+
|
|
360
|
+
let cleanxrefs = cmpxrefs
|
|
361
|
+
.map(xref => {
|
|
362
|
+
|
|
363
|
+
// Redundancy in cmp name, remove request,response suffix
|
|
364
|
+
// const lastPart = getelem(pathStr.split('/'), -1)
|
|
365
|
+
const lastPart = getelem(parts, -1)
|
|
366
|
+
const lastPartLower = lastPart?.toLowerCase()
|
|
367
|
+
const lastPartCanon = canonize(lastPart)
|
|
368
|
+
const origcmpLower = xref.origcmp?.toLowerCase()
|
|
369
|
+
|
|
370
|
+
if (
|
|
371
|
+
'' !== lastPartCanon
|
|
372
|
+
&& (
|
|
373
|
+
xref.cmp === lastPartCanon + '_response'
|
|
374
|
+
|| xref.cmp === lastPartCanon + '_request'
|
|
375
|
+
|| origcmpLower === lastPartLower + 'response'
|
|
376
|
+
|| origcmpLower === lastPartLower + 'request'
|
|
377
|
+
)
|
|
378
|
+
) {
|
|
379
|
+
let cparts = xref.cmp.split('_')
|
|
380
|
+
|
|
381
|
+
// rec-canonize to deal with plural before removed suffix
|
|
382
|
+
xref.cmp = canonize(cparts.slice(0, cparts.length - 1).join('_'))
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return xref
|
|
386
|
+
})
|
|
156
387
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
388
|
+
let goodxrefs = cleanxrefs
|
|
389
|
+
.filter(xref => {
|
|
390
|
+
if (
|
|
391
|
+
cleanxrefs.length <= 1
|
|
392
|
+
|| pathStr.toLowerCase().includes('/' + xref.cmp + '/')
|
|
393
|
+
// || entityOccursInPath(pathStr.toLowerCase(), xref.cmp)
|
|
394
|
+
|| entityOccursInPath(parts, xref.cmp)
|
|
395
|
+
) {
|
|
396
|
+
return true
|
|
162
397
|
}
|
|
163
|
-
|
|
164
|
-
|
|
398
|
+
|
|
399
|
+
// Exclude high frequency suspicious cmps as probably meta data
|
|
400
|
+
const cmprefs = metrics.count.origcmprefs[xref.origcmpref] ?? 0
|
|
401
|
+
const mcount = metrics.count.method
|
|
402
|
+
const pcount = metrics.count.path
|
|
403
|
+
const method_rate = (0 < mcount ? (cmprefs / mcount) : -1)
|
|
404
|
+
const path_rate = (0 < pcount ? (cmprefs / pcount) : -1)
|
|
405
|
+
// console.log('RCN', xref.cmp, cmprefs, mcount, method_rate, IS_ENTCMP_METHOD_RATE, method_rate < IS_ENTCMP_METHOD_RATE)
|
|
406
|
+
const infrequent =
|
|
407
|
+
method_rate < IS_ENTCMP_METHOD_RATE
|
|
408
|
+
|| path_rate < IS_ENTCMP_PATH_RATE
|
|
409
|
+
|
|
410
|
+
if (!infrequent) {
|
|
411
|
+
debugpath(pathStr, methodName, 'CMP-INFREQ',
|
|
412
|
+
xref.val,
|
|
413
|
+
'method:', method_rate, IS_ENTCMP_METHOD_RATE,
|
|
414
|
+
'path:', path_rate, IS_ENTCMP_PATH_RATE
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return infrequent
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
// .sort((a, b) => a.path.length - b.path.length)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
const fcmp: any = goodxrefs[0]
|
|
425
|
+
|
|
426
|
+
let out: MethodEntityDesc | undefined = undefined
|
|
427
|
+
|
|
428
|
+
if (null != fcmp) {
|
|
429
|
+
out = makeMethodEntityDesc({
|
|
430
|
+
ref: fcmp.val,
|
|
431
|
+
cmp: fcmp.cmp,
|
|
432
|
+
origcmp: fcmp.origcmp,
|
|
433
|
+
origcmpref: fcmp.origcmpref,
|
|
434
|
+
entname: fcmp.cmp,
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const tags = methodDef.tags ?? []
|
|
439
|
+
const goodtags = tags.filter((tag: any) => {
|
|
440
|
+
const tagdesc = metrics.found.tag[tag]
|
|
441
|
+
const ctag = tagdesc?.canon
|
|
442
|
+
return (
|
|
443
|
+
!!metrics.found.cmp[ctag] // tag matches a cmp
|
|
444
|
+
|| null == fcmp // there's no cmp, so use tag
|
|
445
|
+
)
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
debugpath(pathStr, methodName, 'TAGS', tags, goodtags, fcmp, methodDef, metrics.found)
|
|
449
|
+
|
|
450
|
+
const ftag = goodtags[0]
|
|
451
|
+
|
|
452
|
+
if (null != ftag) {
|
|
453
|
+
const tagdesc = metrics.found.tag[ftag]
|
|
454
|
+
const tagcmp = metrics.found.cmp[tagdesc.canon]
|
|
455
|
+
|
|
456
|
+
if (tagdesc && (tagcmp || null == fcmp)) {
|
|
457
|
+
if (null == out) {
|
|
458
|
+
out = makeMethodEntityDesc({
|
|
459
|
+
ref: 'tag',
|
|
460
|
+
cmp: tagdesc.canon,
|
|
461
|
+
origcmp: ftag,
|
|
462
|
+
why_cmp,
|
|
463
|
+
entname: tagdesc.canon,
|
|
464
|
+
})
|
|
465
|
+
why_cmp.push('tag=' + out.cmp)
|
|
466
|
+
}
|
|
467
|
+
else if (
|
|
468
|
+
(pathStr.includes('/' + ftag + '/') || pathStr.includes('/' + tagdesc.canon + '/'))
|
|
469
|
+
&& out.cmp !== tagdesc.canon
|
|
470
|
+
) {
|
|
471
|
+
out = makeMethodEntityDesc({
|
|
472
|
+
ref: 'tag',
|
|
473
|
+
cmp: tagdesc.canon,
|
|
474
|
+
origcmp: ftag,
|
|
475
|
+
why_cmp,
|
|
476
|
+
entname: tagdesc.canon,
|
|
477
|
+
})
|
|
478
|
+
why_cmp.push('tag/path=' + out.cmp)
|
|
165
479
|
}
|
|
166
480
|
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (null != out) {
|
|
484
|
+
why_cmp.push('cmp/resolve=' + out.cmp)
|
|
485
|
+
out.why_cmp = why_cmp
|
|
486
|
+
out.cmpoccur = metrics.count.origcmprefs[out.origcmpref ?? ''] ?? 0
|
|
487
|
+
out.path_rate = 0 == metrics.count.path ? -1 : (out.cmpoccur / metrics.count.path)
|
|
488
|
+
out.method_rate = 0 == metrics.count.method ? -1 : (out.cmpoccur / metrics.count.method)
|
|
489
|
+
|
|
490
|
+
methodDef.MethodEntity = out
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
debugpath(pathStr, methodName, 'CMP-NAME', out, origxrefs, cleanxrefs, goodxrefs, goodtags)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
function ResolveEntityName(spec: TaskSpec) {
|
|
499
|
+
const ctx = spec.ctx
|
|
500
|
+
const data = spec.data
|
|
501
|
+
|
|
502
|
+
const mdesc: MethodDesc = spec.node.val
|
|
503
|
+
const methodName = mdesc.method
|
|
504
|
+
const pathStr = mdesc.path
|
|
505
|
+
|
|
506
|
+
const work = spec.data.work
|
|
507
|
+
|
|
508
|
+
const pathDesc = work.pathmap[pathStr]
|
|
509
|
+
const parts = pathDesc.parts
|
|
510
|
+
|
|
511
|
+
work.entity.count.seen++
|
|
512
|
+
|
|
513
|
+
let ment: Partial<MethodEntityDesc>
|
|
514
|
+
ment = mdesc.MethodEntity
|
|
515
|
+
|
|
516
|
+
const why_path: string[] = []
|
|
517
|
+
|
|
518
|
+
if (null == ment) {
|
|
519
|
+
why_path.push('no-desc')
|
|
520
|
+
mdesc.MethodEntity = makeMethodEntityDesc({})
|
|
521
|
+
ment = mdesc.MethodEntity
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
why_path.push(...(ment.why_cmp ?? []))
|
|
525
|
+
|
|
526
|
+
let entname
|
|
527
|
+
|
|
528
|
+
let pm = undefined
|
|
167
529
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
530
|
+
if (pm = pathMatch(parts, 't/p/t/')) {
|
|
531
|
+
entname = entityPathMatch_tpte(data, pm, mdesc, why_path)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
else if (pm = pathMatch(parts, 't/p/')) {
|
|
535
|
+
entname = entityPathMatch_tpe(data, pm, mdesc, why_path)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
else if (pm = pathMatch(parts, 'p/t/')) {
|
|
539
|
+
entname = entityPathMatch_pte(data, pm, mdesc, why_path)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
else if (pm = pathMatch(parts, 't/')) {
|
|
543
|
+
entname = entityPathMatch_te(data, pm, mdesc, why_path)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
else if (pm = pathMatch(parts, 't/p/p')) {
|
|
547
|
+
entname = entityPathMatch_tpp(data, pm, mdesc, why_path)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
else {
|
|
551
|
+
work.entity.count.unresolved++
|
|
552
|
+
entname = 'entity' + work.entity.count.unresolved
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const entdesc = work.entmap[entname] = work.entmap[entname] ?? {
|
|
556
|
+
name: entname,
|
|
557
|
+
id: 'N' + ('' + Math.random()).substring(2, 10),
|
|
558
|
+
op: {},
|
|
559
|
+
why_path,
|
|
560
|
+
...ment
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
entdesc.path = (entdesc.path || {})
|
|
564
|
+
entdesc.path[pathStr] = entdesc.path[pathStr] || {
|
|
565
|
+
rename: { param: {} },
|
|
566
|
+
why_rename: { why_param: {} },
|
|
567
|
+
pm,
|
|
568
|
+
}
|
|
569
|
+
entdesc.path[pathStr].op = entdesc.path[pathStr].op || {}
|
|
570
|
+
entdesc.path[pathStr].why_path = why_path
|
|
571
|
+
|
|
572
|
+
ment.entname = entname
|
|
573
|
+
ment.pm = pm
|
|
574
|
+
|
|
575
|
+
debugpath(pathStr, methodName, 'RESOLVE-ENTITY-NAME',
|
|
576
|
+
formatJSONIC({ entdesc, ment }, { hsepd: 0, $: true, color: true }))
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
function RenameParams(spec: TaskSpec) {
|
|
582
|
+
const ctx = spec.ctx
|
|
583
|
+
const data = spec.data
|
|
584
|
+
const guide = data.guide
|
|
585
|
+
const metrics = guide.metrics
|
|
586
|
+
|
|
587
|
+
const mdesc = spec.node.val
|
|
588
|
+
const ment = mdesc.MethodEntity
|
|
589
|
+
|
|
590
|
+
const pathStr = mdesc.path
|
|
591
|
+
const work = spec.data.work
|
|
592
|
+
|
|
593
|
+
const entname = mdesc.MethodEntity.entname
|
|
594
|
+
const entdesc = work.entmap[entname]
|
|
595
|
+
|
|
596
|
+
const pathdesc = spec.data.work.pathmap[pathStr]
|
|
597
|
+
|
|
598
|
+
const methodName = mdesc.method
|
|
599
|
+
|
|
600
|
+
// Rewrite path parameters that are identifiers to follow the rules:
|
|
601
|
+
// 0. Parameters named [a-z]?id are considered identifiers
|
|
602
|
+
// 1. last identifier is always {id} as this is the primary entity
|
|
603
|
+
// 2. internal identifiers are formatted as {name_id} where name is the parent entity name
|
|
604
|
+
// Example: /api/bar/{id}/zed/{zid}/foo/{fid} ->
|
|
605
|
+
// /api/bar/{bar_id}/zed/{zed_id}/foo/{id}
|
|
606
|
+
|
|
607
|
+
// id needs to be t/p/
|
|
608
|
+
const multParamEndMatch = pathMatch(mdesc.path, 'p/p/')
|
|
609
|
+
if (multParamEndMatch) {
|
|
610
|
+
return
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
const pathDesc = entdesc.path[pathStr]
|
|
615
|
+
pathDesc.rename = (pathDesc.rename ?? { param: {} })
|
|
616
|
+
pathDesc.why_rename = (pathDesc.why_rename ?? { why_param: {} })
|
|
617
|
+
|
|
618
|
+
pathDesc.action = (pathDesc.action ?? {})
|
|
619
|
+
pathDesc.why_action = (pathDesc.why_action ?? {})
|
|
620
|
+
|
|
621
|
+
const paramRenameCapture = {
|
|
622
|
+
rename: pathDesc.rename.param = (pathDesc.rename.param ?? {}),
|
|
623
|
+
why: pathDesc.why_rename.why_param = (pathDesc.why_rename.why_param ?? {}),
|
|
624
|
+
}
|
|
625
|
+
const parts = pathdesc.parts
|
|
626
|
+
|
|
627
|
+
const cmpname = mdesc.cmp
|
|
628
|
+
const considerCmp =
|
|
629
|
+
null != cmpname &&
|
|
630
|
+
0 < metrics.count.uniqschema &&
|
|
631
|
+
mdesc.method_rate < IS_ENTCMP_METHOD_RATE
|
|
632
|
+
|
|
633
|
+
const origParams = []
|
|
634
|
+
|
|
635
|
+
for (let partI = 0; partI < parts.length; partI++) {
|
|
636
|
+
let partStr = parts[partI]
|
|
637
|
+
|
|
638
|
+
if (isParam(partStr)) {
|
|
639
|
+
origParams.push(partStr.replace(/[\}\{\*]/g, ''))
|
|
640
|
+
|
|
641
|
+
const why = []
|
|
642
|
+
|
|
643
|
+
const oldParam = partStr.substring(1, partStr.length - 1)
|
|
644
|
+
paramRenameCapture.why[oldParam] = (paramRenameCapture.why[oldParam] ?? [])
|
|
645
|
+
|
|
646
|
+
const lastPart = partI === parts.length - 1
|
|
647
|
+
const secondLastPart = partI === parts.length - 2
|
|
648
|
+
const notLastPart = partI < parts.length - 1
|
|
649
|
+
const hasParent = 0 < partI && !isParam(parts[partI - 1])
|
|
650
|
+
const parentName = hasParent ? canonize(parts[partI - 1]) : null
|
|
651
|
+
const not_exact_id = 'id' !== oldParam
|
|
652
|
+
const probably_an_id =
|
|
653
|
+
oldParam.endsWith('id')
|
|
654
|
+
|| oldParam.endsWith('Id')
|
|
655
|
+
|| canonize(oldParam) === parentName
|
|
656
|
+
|
|
657
|
+
debugpath(pathStr, mdesc.method, 'RENAME-PARAM-PART', parts, partI, partStr, {
|
|
658
|
+
lastPart,
|
|
659
|
+
secondLastPart,
|
|
660
|
+
notLastPart,
|
|
661
|
+
hasParent,
|
|
662
|
+
parentName,
|
|
663
|
+
not_exact_id,
|
|
664
|
+
probably_an_id,
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
// Id-like not at end, and after a possible entname.
|
|
668
|
+
// .../parentent/{id}/...
|
|
669
|
+
if (
|
|
670
|
+
probably_an_id
|
|
671
|
+
&& hasParent
|
|
672
|
+
&& notLastPart
|
|
673
|
+
) {
|
|
674
|
+
why.push('maybe-parent')
|
|
675
|
+
|
|
676
|
+
// actually an action
|
|
677
|
+
if (
|
|
678
|
+
secondLastPart
|
|
679
|
+
&& (
|
|
680
|
+
(
|
|
681
|
+
parentName !== entdesc.name
|
|
682
|
+
&& entdesc.name.startsWith(parentName + '_')
|
|
683
|
+
)
|
|
684
|
+
// || parentName === cmp.name
|
|
685
|
+
|| parentName === cmpname
|
|
686
|
+
)
|
|
687
|
+
) {
|
|
688
|
+
// let newParamName = 'id'
|
|
689
|
+
updateParamRename(
|
|
690
|
+
ctx, data, pathStr, methodName, paramRenameCapture, oldParam,
|
|
691
|
+
'id', 'action-parent:' + entdesc.name)
|
|
692
|
+
why.push('action')
|
|
693
|
+
|
|
694
|
+
updateAction(methodName, oldParam,
|
|
695
|
+
parts[partI + 1], entdesc, pathDesc, 'action-not-parent')
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
else if (hasParent && parentName === cmpname) {
|
|
699
|
+
updateParamRename(
|
|
700
|
+
ctx, data, pathStr, methodName, paramRenameCapture, oldParam,
|
|
701
|
+
'id', 'id-parent-cmp')
|
|
702
|
+
why.push('id-parent-cmp')
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
else if (hasParent && parentName === entdesc.name) {
|
|
706
|
+
updateParamRename(
|
|
707
|
+
ctx, data, pathStr, methodName, paramRenameCapture, oldParam,
|
|
708
|
+
'id', 'id-parent-ent')
|
|
709
|
+
why.push('id-parent-ent')
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
else {
|
|
713
|
+
updateParamRename(
|
|
714
|
+
ctx, data, pathStr, methodName, paramRenameCapture, oldParam,
|
|
715
|
+
parentName + '_id', 'parent:' + parentName)
|
|
716
|
+
why.push('parent')
|
|
717
|
+
}
|
|
172
718
|
}
|
|
173
|
-
|
|
174
|
-
|
|
719
|
+
|
|
720
|
+
// /api/foo/{foo}/bar/...
|
|
721
|
+
// param matches parent entname, but is not _id format
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
// At end, but not called id.
|
|
725
|
+
// .../ent/{not-id}
|
|
726
|
+
else if (
|
|
727
|
+
lastPart
|
|
728
|
+
&& not_exact_id
|
|
729
|
+
&& (!hasParent
|
|
730
|
+
|| (
|
|
731
|
+
parentName === entdesc.name
|
|
732
|
+
|| entdesc.name.endsWith('_' + parentName)
|
|
733
|
+
)
|
|
734
|
+
)
|
|
735
|
+
&& (!considerCmp || cmpname === entdesc.name)
|
|
736
|
+
) {
|
|
737
|
+
updateParamRename(
|
|
738
|
+
ctx, data, pathStr, methodName, paramRenameCapture, oldParam,
|
|
739
|
+
'id', 'end-id;' + methodName + ';parent=' + hasParent + '/' + parentName +
|
|
740
|
+
';cmp=' + considerCmp + (null == cmpname ? '' : '/' + cmpname))
|
|
741
|
+
why.push('end-id')
|
|
175
742
|
}
|
|
176
743
|
|
|
744
|
+
// Mot at end, has preceding non-param part.
|
|
745
|
+
// .../parentent/{paramname}/...
|
|
746
|
+
else if (
|
|
747
|
+
notLastPart
|
|
748
|
+
&& 1 < partI
|
|
749
|
+
&& hasParent
|
|
750
|
+
) {
|
|
751
|
+
why.push('has-parent')
|
|
752
|
+
|
|
753
|
+
// Actually primary ent with an action$ suffix
|
|
754
|
+
if (
|
|
755
|
+
secondLastPart
|
|
756
|
+
) {
|
|
757
|
+
why.push('second-last')
|
|
758
|
+
|
|
759
|
+
if (
|
|
760
|
+
'id' !== oldParam
|
|
761
|
+
// && fixEntName(partStr) === entdesc.name
|
|
762
|
+
&& canonize(partStr) === entdesc.name
|
|
763
|
+
) {
|
|
764
|
+
updateParamRename(
|
|
765
|
+
ctx, data, pathStr, methodName, paramRenameCapture, oldParam,
|
|
766
|
+
'id', 'end-action')
|
|
767
|
+
why.push('end-action')
|
|
768
|
+
|
|
769
|
+
updateAction(methodName, oldParam,
|
|
770
|
+
parts[partI + 1], entdesc, pathDesc, 'end-action')
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
why.push('not-end-action')
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Primary ent id not at end!
|
|
778
|
+
else if (
|
|
779
|
+
hasParent
|
|
780
|
+
&& parentName === cmpname
|
|
781
|
+
) {
|
|
782
|
+
updateParamRename(
|
|
783
|
+
ctx, data, pathStr, methodName, paramRenameCapture, oldParam,
|
|
784
|
+
'id', 'id-not-last')
|
|
785
|
+
|
|
786
|
+
why.push('id-not-last')
|
|
787
|
+
// paramRenames[oldParam] = 'id'
|
|
788
|
+
// paramRenamesWhy[oldParam].push('id-not-last')
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Not primary ent.
|
|
792
|
+
else {
|
|
793
|
+
why.push('default')
|
|
794
|
+
|
|
795
|
+
let newParamName = parentName + '_id'
|
|
796
|
+
if (newParamName != oldParam) {
|
|
797
|
+
updateParamRename(
|
|
798
|
+
ctx, data, pathStr, methodName, paramRenameCapture, oldParam,
|
|
799
|
+
newParamName, 'not-primary')
|
|
800
|
+
why.push('not-primary')
|
|
801
|
+
|
|
802
|
+
// paramRenames[oldParam] = newParamName
|
|
803
|
+
// paramRenamesWhy[oldParam].push('not-primary')
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
why.push('done')
|
|
809
|
+
|
|
810
|
+
if (paramRenameCapture.rename[oldParam] === oldParam) {
|
|
811
|
+
why.push('delete-dup')
|
|
812
|
+
delete paramRenameCapture.rename[oldParam]
|
|
813
|
+
delete paramRenameCapture.why[oldParam]
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// TODO: these need to done via an API
|
|
817
|
+
debugpath(pathStr, methodName, 'RENAME-PARAM',
|
|
818
|
+
{
|
|
819
|
+
pathStr,
|
|
820
|
+
methodName,
|
|
821
|
+
partStr,
|
|
822
|
+
why,
|
|
823
|
+
oldParam,
|
|
824
|
+
lastPart,
|
|
825
|
+
secondLastPart,
|
|
826
|
+
notLastPart,
|
|
827
|
+
hasParent,
|
|
828
|
+
parentName,
|
|
829
|
+
not_exact_id,
|
|
830
|
+
probably_an_id,
|
|
831
|
+
considerCmp,
|
|
832
|
+
cmp: mdesc.cmp,
|
|
833
|
+
cmpname,
|
|
834
|
+
paramRenameCapture,
|
|
835
|
+
entdesc
|
|
836
|
+
}
|
|
837
|
+
)
|
|
177
838
|
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
ment.rename = paramRenameCapture.rename
|
|
842
|
+
ment.why_rename = paramRenameCapture.why
|
|
843
|
+
ment.rename_orig = origParams
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
function FindActions(spec: TaskSpec) {
|
|
848
|
+
const mdesc = spec.node.val
|
|
849
|
+
const pathStr = mdesc.path
|
|
850
|
+
const work = spec.data.work
|
|
851
|
+
|
|
852
|
+
const ment = mdesc.MethodEntity
|
|
853
|
+
const entname = ment.entname
|
|
854
|
+
const entdesc = work.entmap[entname]
|
|
855
|
+
|
|
856
|
+
// const pathdesc = spec.data.work.pathmap[pathStr]
|
|
857
|
+
const pathdesc = entdesc.path[pathStr]
|
|
858
|
+
|
|
859
|
+
const methodName = mdesc.method
|
|
860
|
+
|
|
861
|
+
pathdesc.action = (pathdesc.action ?? {})
|
|
862
|
+
pathdesc.why_action = (pathdesc.why_action ?? {})
|
|
863
|
+
|
|
864
|
+
const parts = spec.data.work.pathmap[pathStr].parts
|
|
178
865
|
|
|
179
|
-
|
|
866
|
+
const fourthLastPart = parts[parts.length - 4]
|
|
867
|
+
const fourthLastPartCanon = canonize(fourthLastPart)
|
|
868
|
+
const thirdLastPart = parts[parts.length - 3]
|
|
869
|
+
const thirdLastPartCanon = canonize(thirdLastPart)
|
|
870
|
+
const secondLastPart = parts[parts.length - 2]
|
|
871
|
+
const secondLastPartCanon = canonize(secondLastPart)
|
|
872
|
+
const lastPart = parts[parts.length - 1]
|
|
873
|
+
const lastPartCanon = canonize(lastPart)
|
|
180
874
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
875
|
+
const cmp = ment.cmp
|
|
876
|
+
|
|
877
|
+
// /api/foo/bar where foo is the entity and bar is the action, no id param
|
|
878
|
+
if (
|
|
879
|
+
secondLastPartCanon === cmp
|
|
880
|
+
|| secondLastPartCanon === ment.origcmp
|
|
881
|
+
|| secondLastPartCanon === entname
|
|
882
|
+
) {
|
|
883
|
+
if (!isParam(lastPart)) {
|
|
884
|
+
updateAction(methodName, lastPart, lastPartCanon, entdesc, pathdesc, 'no-param')
|
|
185
885
|
}
|
|
886
|
+
}
|
|
186
887
|
|
|
187
|
-
|
|
188
|
-
|
|
888
|
+
// /api/foo/{param}/action
|
|
889
|
+
else if (
|
|
890
|
+
thirdLastPartCanon === cmp
|
|
891
|
+
|| thirdLastPartCanon === ment.origcmp
|
|
892
|
+
|| thirdLastPartCanon === entname
|
|
893
|
+
) {
|
|
894
|
+
if (isParam(secondLastPart) && !isParam(lastPart)) {
|
|
895
|
+
updateAction(methodName, lastPart, lastPartCanon, entdesc, pathdesc,
|
|
896
|
+
'ent-param-2nd-last')
|
|
189
897
|
}
|
|
898
|
+
}
|
|
190
899
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
900
|
+
// /api/foo/{param}/action/subaction
|
|
901
|
+
else if (
|
|
902
|
+
fourthLastPartCanon === cmp
|
|
903
|
+
|| fourthLastPartCanon === ment.origcmp
|
|
904
|
+
|| fourthLastPartCanon === entname
|
|
905
|
+
) {
|
|
906
|
+
if (isParam(thirdLastPart) && !isParam(secondLastPart) && !isParam(lastPart)) {
|
|
907
|
+
const oldActionName = secondLastPart + '/' + lastPart
|
|
908
|
+
const actionName = secondLastPartCanon + '_' + lastPartCanon
|
|
909
|
+
updateAction(methodName, oldActionName, actionName, entdesc, pathdesc,
|
|
910
|
+
'ent-param-3rd-last')
|
|
911
|
+
}
|
|
912
|
+
}
|
|
198
913
|
|
|
199
|
-
|
|
200
|
-
// console.dir(entityDescs.user, { depth: null })
|
|
914
|
+
debugpath(pathStr, methodName, 'FIND-ACTIONS', cmp, parts, pathdesc.action, pathdesc.why_action)
|
|
201
915
|
|
|
202
|
-
return
|
|
916
|
+
// return pathdesc.action
|
|
203
917
|
}
|
|
204
918
|
|
|
205
919
|
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
pathStr: string,
|
|
210
|
-
methodDef: Record<string, any>,
|
|
211
|
-
methodStr: string,
|
|
212
|
-
why_ent: string[]
|
|
213
|
-
): EntityDesc | undefined {
|
|
920
|
+
function ResolveOperation(spec: TaskSpec) {
|
|
921
|
+
const mdesc: MethodDesc = spec.node.val
|
|
922
|
+
const ment: MethodEntityDesc = mdesc.MethodEntity
|
|
214
923
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
let origentname: string = ''
|
|
924
|
+
const pathStr = mdesc.path
|
|
925
|
+
const work = spec.data.work
|
|
218
926
|
|
|
219
|
-
const
|
|
927
|
+
const parts: string[] = work.pathmap[pathStr].parts
|
|
220
928
|
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
929
|
+
const entname = mdesc.MethodEntity.entname
|
|
930
|
+
const entdesc = work.entmap[entname]
|
|
931
|
+
|
|
932
|
+
const methodName = mdesc.method
|
|
933
|
+
|
|
934
|
+
const why_op: string[] = ment.why_op = []
|
|
935
|
+
|
|
936
|
+
let opname = METHOD_IDOP[methodName]
|
|
937
|
+
let standard_opname = opname
|
|
938
|
+
|
|
939
|
+
if (null == opname) {
|
|
940
|
+
why_op.push('no-op:' + methodName)
|
|
941
|
+
return
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// REVIEW: using POST and PUT in non-restian ways is too wierd to handle consistently
|
|
945
|
+
// correct using guide customizations
|
|
946
|
+
|
|
947
|
+
// Sometimes POST is used to update, not create. Attempt to identify this.
|
|
948
|
+
// And sometimes vice versa for PUT
|
|
949
|
+
// const id_param_offset = ment.pm?.expr?.endsWith('/t/') ? 1 : 0
|
|
950
|
+
// const has_end_id_param =
|
|
951
|
+
// entname == canonize(parts[parts.length - 2 - id_param_offset])
|
|
952
|
+
// && parts[parts.length - 1 - id_param_offset]?.toLowerCase().endsWith('id}')
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
if ('load' === standard_opname) {
|
|
956
|
+
const islist = isListResponse(mdesc, pathStr, why_op)
|
|
957
|
+
opname = islist ? 'list' : opname
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/*
|
|
961
|
+
else if (
|
|
962
|
+
'create' === standard_opname
|
|
963
|
+
&& has_end_id_param
|
|
964
|
+
) {
|
|
965
|
+
opname = 'update'
|
|
966
|
+
why_op.push('id-present')
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
else if (
|
|
970
|
+
'update' === standard_opname
|
|
971
|
+
&& !has_end_id_param
|
|
972
|
+
) {
|
|
973
|
+
opname = 'create'
|
|
974
|
+
why_op.push('no-id-present')
|
|
975
|
+
}
|
|
976
|
+
*/
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
else {
|
|
980
|
+
why_op.push('not-load')
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// why.push('ent=' + entdesc.name)
|
|
984
|
+
|
|
985
|
+
ment.opname = opname
|
|
986
|
+
ment.why_opname = why_op
|
|
987
|
+
|
|
988
|
+
const op = entdesc.path[pathStr].op
|
|
989
|
+
|
|
990
|
+
const opdef = {
|
|
991
|
+
method: methodName,
|
|
992
|
+
why_op: why_op.join(';')
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (null == op[opname]) {
|
|
996
|
+
op[opname] = opdef
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Conflicting methods for same operation
|
|
1000
|
+
// METHOD_CONSIDER_ORDER wins
|
|
1001
|
+
// Add operation using method name
|
|
1002
|
+
else {
|
|
1003
|
+
op[methodName.toLowerCase()] = opdef
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
debugpath(pathStr, methodName, 'ResolveOperation', standard_opname, opname, why_op, op)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
function ResolveTransform(spec: TaskSpec) {
|
|
1011
|
+
const mdesc = spec.node.val
|
|
1012
|
+
const ment = mdesc.MethodEntity
|
|
225
1013
|
|
|
1014
|
+
const pathStr = mdesc.path
|
|
1015
|
+
const work = spec.data.work
|
|
226
1016
|
|
|
227
|
-
|
|
228
|
-
|
|
1017
|
+
const entname = mdesc.MethodEntity.entname
|
|
1018
|
+
const entdesc = work.entmap[entname]
|
|
229
1019
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
1020
|
+
// const pathdesc = spec.data.work.pathmap[pathStr]
|
|
1021
|
+
const pathdesc = entdesc.path[pathStr]
|
|
1022
|
+
|
|
1023
|
+
const methodName = mdesc.method
|
|
1024
|
+
|
|
1025
|
+
const opname = ment.opname
|
|
1026
|
+
|
|
1027
|
+
const op = pathdesc.op
|
|
1028
|
+
|
|
1029
|
+
// Only specify transforms if they are not defaults
|
|
1030
|
+
const transform: Record<string, any> = {
|
|
1031
|
+
req: undefined,
|
|
1032
|
+
res: undefined,
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const resokdef = mdesc.responses?.[200] || mdesc.responses?.[201]
|
|
1036
|
+
const resprops = getResponseSchema(resokdef)?.properties
|
|
1037
|
+
debugpath(pathStr, methodName, 'TRANSFORM-RES', keysof(resprops))
|
|
1038
|
+
|
|
1039
|
+
// console.log('APIDEF-resprops', resprops)
|
|
1040
|
+
|
|
1041
|
+
if (resprops) {
|
|
1042
|
+
if (resprops[entdesc.origname]) {
|
|
1043
|
+
transform.res = '`body.' + entdesc.origname + '`'
|
|
236
1044
|
}
|
|
237
|
-
else {
|
|
238
|
-
|
|
239
|
-
why_name.push('path:' + m[1])
|
|
1045
|
+
else if (resprops[entdesc.name]) {
|
|
1046
|
+
transform.res = '`body.' + entdesc.name + '`'
|
|
240
1047
|
}
|
|
1048
|
+
}
|
|
241
1049
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
1050
|
+
const reqprops = getRequestBodySchema(mdesc.requestBody)
|
|
1051
|
+
debugpath(pathStr, methodName, 'TRANSFORM-REQ', keysof(reqprops))
|
|
1052
|
+
if (reqprops) {
|
|
1053
|
+
if (reqprops[entdesc.origname]) {
|
|
1054
|
+
transform.req = { [entdesc.origname]: '`reqdata`' }
|
|
1055
|
+
}
|
|
1056
|
+
else if (reqprops[entdesc.name]) {
|
|
1057
|
+
transform.req = { [entdesc.name]: '`reqdata`' }
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
247
1060
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
1061
|
+
if (!isempty(transform)) {
|
|
1062
|
+
op[opname].transform = transform
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
function BuildEntity(spec: TaskSpec) {
|
|
1069
|
+
const entdesc = spec.node.val
|
|
1070
|
+
// console.log('BUILD-ENTITY')
|
|
1071
|
+
// console.dir(entdesc, { depth: null })
|
|
1072
|
+
|
|
1073
|
+
const guide: Guide = spec.data.guide
|
|
1074
|
+
guide.metrics.count.entity++
|
|
1075
|
+
|
|
1076
|
+
const entityMap: Record<string, GuideEntity> = guide.entity
|
|
1077
|
+
|
|
1078
|
+
const path: Record<string, GuidePath> = {}
|
|
1079
|
+
|
|
1080
|
+
const rename_param = (pathdesc: any) => {
|
|
1081
|
+
// console.log('RENAME-PATHDESC', pathdesc)
|
|
1082
|
+
const out: Record<string, GuideRenameParam> = {}
|
|
1083
|
+
each(pathdesc.rename.param, (item: any) => {
|
|
1084
|
+
out[item.key$] = {
|
|
1085
|
+
target: item.val$,
|
|
1086
|
+
why_rename: pathdesc.why_rename.why_param[item.key$]
|
|
253
1087
|
}
|
|
1088
|
+
})
|
|
1089
|
+
return out
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
each(entdesc.path, (pathdesc: any, pathstr: string) => {
|
|
1093
|
+
const guidepath = {
|
|
1094
|
+
why_path: pathdesc.why_path,
|
|
1095
|
+
action: pathdesc.action,
|
|
1096
|
+
rename: {
|
|
1097
|
+
param: rename_param(pathdesc)
|
|
1098
|
+
},
|
|
1099
|
+
op: pathdesc.op
|
|
254
1100
|
}
|
|
1101
|
+
path[pathstr] = guidepath
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
entityMap[entdesc.name] = {
|
|
1105
|
+
name: entdesc.name,
|
|
1106
|
+
orig: entdesc.origcmp,
|
|
1107
|
+
path,
|
|
255
1108
|
}
|
|
256
1109
|
|
|
257
|
-
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
function entityPathMatch_tpte(
|
|
1119
|
+
data: { def: any, guide: any, work: any },
|
|
1120
|
+
pm: PathMatch,
|
|
1121
|
+
mdesc: any,
|
|
1122
|
+
why: string[]
|
|
1123
|
+
) {
|
|
1124
|
+
const ment = mdesc.MethodEntity
|
|
1125
|
+
|
|
1126
|
+
const pathNameIndex = 2
|
|
1127
|
+
|
|
1128
|
+
why.push('path=t/p/t/')
|
|
1129
|
+
const origPathName = pm[pathNameIndex]
|
|
1130
|
+
let entname = canonize(origPathName)
|
|
1131
|
+
let ecm = undefined
|
|
1132
|
+
|
|
1133
|
+
if (null != ment.cmp) {
|
|
1134
|
+
ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1135
|
+
entname = ecm.name
|
|
1136
|
+
why.push('has-cmp=' + ecm.orig)
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
else if (probableEntityMethod(data, ment, pm, why)) {
|
|
1140
|
+
ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1141
|
+
if (ecm.cmpish) {
|
|
1142
|
+
entname = ecm.name
|
|
1143
|
+
why.push('prob-ent=' + ecm.orig)
|
|
1144
|
+
}
|
|
1145
|
+
else if (endsWithCmp(data, pm)) {
|
|
1146
|
+
entname = canonize(getelem(pm, -1))
|
|
1147
|
+
why.push('prob-ent-last=' + ecm.orig)
|
|
1148
|
+
}
|
|
1149
|
+
else if (0 < findPathsWithPrefix(data, pm.path, { strict: true })) {
|
|
1150
|
+
entname = canonize(getelem(pm, -1))
|
|
1151
|
+
why.push('prob-ent-prefix=' + ecm.orig)
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
entname = canonize(getelem(pm, -3)) + '_' + entname
|
|
1155
|
+
why.push('prob-ent-part')
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Probably an entity action suffix
|
|
258
1160
|
else {
|
|
259
|
-
|
|
260
|
-
|
|
1161
|
+
why.push('prob-ent-act')
|
|
1162
|
+
entname = canonize(getelem(pm, -3))
|
|
261
1163
|
}
|
|
262
1164
|
|
|
1165
|
+
return entname
|
|
1166
|
+
}
|
|
263
1167
|
|
|
264
|
-
|
|
265
|
-
|
|
1168
|
+
function endsWithCmp(data: any, pm: PathMatch) {
|
|
1169
|
+
const last = canonize(getelem(pm, -1))
|
|
1170
|
+
return isOrigCmp(data, last)
|
|
1171
|
+
}
|
|
266
1172
|
|
|
267
|
-
names(entdesc, entname)
|
|
268
1173
|
|
|
269
|
-
|
|
1174
|
+
function isOrigCmp(data: any, name: string) {
|
|
1175
|
+
return null != data.metrics.count.origcmprefs[name]
|
|
1176
|
+
}
|
|
270
1177
|
|
|
271
|
-
entdesc.path = (entdesc.path || {})
|
|
272
|
-
entdesc.path[pathStr] = entdesc.path[pathStr] || {}
|
|
273
|
-
entdesc.path[pathStr].op = entdesc.path[pathStr].op || {}
|
|
274
1178
|
|
|
275
|
-
|
|
276
|
-
|
|
1179
|
+
function entityOccursInPath(parts: string[], entname: string) {
|
|
1180
|
+
let partsLower = parts.map(p => p.toLowerCase())
|
|
1181
|
+
partsLower = partsLower.filter(p => '{' !== p[0]).map(p => canonize(p))
|
|
1182
|
+
return !partsLower.reduce((a: boolean, p: string) => (a && p !== entname), true)
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
function entityPathMatch_tpe(
|
|
1187
|
+
data: { def: any, guide: any, work: any },
|
|
1188
|
+
pm: PathMatch, mdesc: any, why: string[]
|
|
1189
|
+
) {
|
|
1190
|
+
const ment = mdesc.MethodEntity
|
|
1191
|
+
const pathNameIndex = 0
|
|
1192
|
+
|
|
1193
|
+
why.push('path=t/p/')
|
|
1194
|
+
const origPathName = pm[pathNameIndex]
|
|
1195
|
+
// let entname = fixEntName(origPathName)
|
|
1196
|
+
let entname = canonize(origPathName)
|
|
1197
|
+
|
|
1198
|
+
if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
|
|
1199
|
+
let ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1200
|
+
entname = ecm.name
|
|
1201
|
+
}
|
|
1202
|
+
else {
|
|
1203
|
+
why.push('ent-act')
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
return entname
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
function entityPathMatch_pte(
|
|
1211
|
+
data: { def: any, guide: any, work: any },
|
|
1212
|
+
pm: PathMatch, mdesc: any, why: string[]
|
|
1213
|
+
) {
|
|
1214
|
+
const ment = mdesc.MethodEntity
|
|
1215
|
+
const pathNameIndex = 1
|
|
1216
|
+
|
|
1217
|
+
why.push('path=p/t/')
|
|
1218
|
+
const origPathName = pm[pathNameIndex]
|
|
1219
|
+
let entname = canonize(origPathName)
|
|
1220
|
+
|
|
1221
|
+
if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
|
|
1222
|
+
let ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1223
|
+
entname = ecm.name
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
why.push('ent-act')
|
|
277
1227
|
}
|
|
278
1228
|
|
|
279
|
-
return
|
|
1229
|
+
return entname
|
|
280
1230
|
}
|
|
281
1231
|
|
|
282
1232
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
1233
|
+
function entityPathMatch_te(
|
|
1234
|
+
data: { def: any, guide: any, work: any },
|
|
1235
|
+
pm: PathMatch, mdesc: any, why: string[]
|
|
1236
|
+
) {
|
|
1237
|
+
const ment = mdesc.MethodEntity
|
|
1238
|
+
const pathNameIndex = 0
|
|
1239
|
+
|
|
1240
|
+
why.push('path=t/')
|
|
1241
|
+
const origPathName = pm[pathNameIndex]
|
|
1242
|
+
// let entname = fixEntName(origPathName)
|
|
1243
|
+
let entname = canonize(origPathName)
|
|
1244
|
+
|
|
1245
|
+
if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
|
|
1246
|
+
let ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1247
|
+
entname = ecm.name
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
why.push('ent-act')
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
return entname
|
|
288
1254
|
}
|
|
289
1255
|
|
|
290
1256
|
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
): string | undefined {
|
|
298
|
-
let compname: string | undefined = undefined
|
|
1257
|
+
function entityPathMatch_tpp(
|
|
1258
|
+
data: { def: any, guide: any, work: any },
|
|
1259
|
+
pm: PathMatch, mdesc: any, why: string[]
|
|
1260
|
+
) {
|
|
1261
|
+
const ment = mdesc.MethodEntity
|
|
1262
|
+
const pathNameIndex = 0
|
|
299
1263
|
|
|
300
|
-
|
|
301
|
-
|
|
1264
|
+
why.push('path=t/p/p')
|
|
1265
|
+
const origPathName = pm[pathNameIndex]
|
|
1266
|
+
// let entname = fixEntName(origPathName)
|
|
1267
|
+
let entname = canonize(origPathName)
|
|
302
1268
|
|
|
303
|
-
|
|
304
|
-
|
|
1269
|
+
if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
|
|
1270
|
+
let ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1271
|
+
entname = ecm.name
|
|
1272
|
+
}
|
|
1273
|
+
else {
|
|
1274
|
+
why.push('ent-act')
|
|
1275
|
+
}
|
|
305
1276
|
|
|
306
|
-
|
|
1277
|
+
return entname
|
|
1278
|
+
}
|
|
307
1279
|
|
|
308
|
-
// console.log('RCN', pathStr, methodStr, xrefs.map(x => [x.val, x.path.length]))
|
|
309
1280
|
|
|
310
|
-
|
|
1281
|
+
function getRequestBodySchema(requestBody: any) {
|
|
1282
|
+
return requestBody?.content?.['application/json']?.schema ??
|
|
1283
|
+
requestBody?.schema
|
|
1284
|
+
}
|
|
311
1285
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
1286
|
+
function getResponseSchema(response: any) {
|
|
1287
|
+
return response?.content?.['application/json']?.schema ??
|
|
1288
|
+
response?.schema
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
// No entity component was found, but there still might be an entity.
|
|
1293
|
+
function probableEntityMethod(
|
|
1294
|
+
data: { def: any },
|
|
1295
|
+
mdesc: any,
|
|
1296
|
+
pm: PathMatch,
|
|
1297
|
+
why: string[]
|
|
1298
|
+
) {
|
|
1299
|
+
const request = mdesc.requestBody
|
|
1300
|
+
const reqSchema = getRequestBodySchema(request)
|
|
1301
|
+
|
|
1302
|
+
const response = mdesc.responses?.['201'] || mdesc.responses?.['200']
|
|
1303
|
+
const resSchema = getResponseSchema(response)
|
|
1304
|
+
const noResponse = null == resSchema && null != mdesc.responses?.['204']
|
|
1305
|
+
|
|
1306
|
+
let prob_why = ''
|
|
1307
|
+
|
|
1308
|
+
let probent = false
|
|
1309
|
+
|
|
1310
|
+
if (noResponse) {
|
|
1311
|
+
// No response at all means not an action, thus probably an entity.
|
|
1312
|
+
prob_why = 'nores'
|
|
1313
|
+
probent = true
|
|
318
1314
|
}
|
|
319
1315
|
|
|
320
|
-
if (null !=
|
|
321
|
-
|
|
1316
|
+
else if (null != reqSchema) {
|
|
1317
|
+
if (
|
|
1318
|
+
'POST' === mdesc.method
|
|
1319
|
+
&& !pm.expr.endsWith('/p/')
|
|
1320
|
+
|
|
1321
|
+
// A real entity would probably occur in at least one other t/p path
|
|
1322
|
+
// otherwise this is probably an action
|
|
1323
|
+
&& (1 < Object.keys(data.def.paths).filter(path =>
|
|
1324
|
+
path.includes('/' + pm[pm.length - 1] + '/')).length)
|
|
1325
|
+
) {
|
|
1326
|
+
prob_why = 'post'
|
|
1327
|
+
probent = true
|
|
1328
|
+
}
|
|
322
1329
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
1330
|
+
else if (
|
|
1331
|
+
('PUT' === mdesc.method || 'PATCH' === mdesc.method)
|
|
1332
|
+
&& pm.expr.endsWith('/p/')
|
|
1333
|
+
) {
|
|
1334
|
+
prob_why = 'putish'
|
|
1335
|
+
probent = true
|
|
326
1336
|
}
|
|
327
1337
|
}
|
|
1338
|
+
else if ('GET' === mdesc.method) {
|
|
1339
|
+
prob_why = 'get'
|
|
1340
|
+
probent = true
|
|
1341
|
+
}
|
|
328
1342
|
|
|
329
|
-
|
|
330
|
-
}
|
|
1343
|
+
const rescodes = Object.keys(mdesc.responses ?? {})
|
|
331
1344
|
|
|
1345
|
+
debugpath(mdesc.path, mdesc.method, 'PROBABLE-ENTITY-RESPONSE',
|
|
1346
|
+
{ mdesc, responses: rescodes, probent, prob_why })
|
|
332
1347
|
|
|
333
|
-
|
|
334
|
-
methodStr: string,
|
|
335
|
-
methodDef: any,
|
|
336
|
-
pathStr: string,
|
|
337
|
-
entdesc: EntityDesc,
|
|
338
|
-
why: string[]
|
|
339
|
-
)
|
|
340
|
-
: string | undefined {
|
|
341
|
-
// console.log('ROP', pathStr, methodDef)
|
|
1348
|
+
why.push('entres=' + probent + '/' + rescodes + ('' === prob_why ? '' : '/' + prob_why))
|
|
342
1349
|
|
|
1350
|
+
return probent
|
|
1351
|
+
}
|
|
343
1352
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
1353
|
+
|
|
1354
|
+
function entityCmpMatch(
|
|
1355
|
+
data: { def: any, guide: any, work: any },
|
|
1356
|
+
entname: string,
|
|
1357
|
+
mdesc: any,
|
|
1358
|
+
why: string[]
|
|
1359
|
+
): {
|
|
1360
|
+
name: string,
|
|
1361
|
+
orig: string,
|
|
1362
|
+
cmpish: boolean,
|
|
1363
|
+
pathish: boolean,
|
|
1364
|
+
} {
|
|
1365
|
+
const ment = mdesc.MethodEntity
|
|
1366
|
+
|
|
1367
|
+
let out = {
|
|
1368
|
+
name: entname,
|
|
1369
|
+
orig: ment.origcmp ?? entname,
|
|
1370
|
+
cmpish: false,
|
|
1371
|
+
pathish: true,
|
|
348
1372
|
}
|
|
349
1373
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
1374
|
+
// console.log('ECM-A', out, ment)
|
|
1375
|
+
|
|
1376
|
+
const cmpInfrequent = (
|
|
1377
|
+
ment.method_rate < IS_ENTCMP_METHOD_RATE
|
|
1378
|
+
|| ment.path_rate < IS_ENTCMP_PATH_RATE
|
|
1379
|
+
)
|
|
1380
|
+
|
|
1381
|
+
if (
|
|
1382
|
+
null != ment.cmp
|
|
1383
|
+
&& entname != ment.cmp
|
|
1384
|
+
&& !ment.cmp.startsWith(entname)
|
|
1385
|
+
) {
|
|
1386
|
+
if (cmpInfrequent) {
|
|
1387
|
+
why.push('cmp-primary')
|
|
1388
|
+
out.name = ment.cmp
|
|
1389
|
+
out.orig = ment.origcmp
|
|
1390
|
+
out.cmpish = true
|
|
1391
|
+
out.pathish = false
|
|
1392
|
+
why.push('cmp-infreq')
|
|
1393
|
+
}
|
|
1394
|
+
else if (cmpOccursInPath(data, ment.cmp)) {
|
|
1395
|
+
why.push('cmp-path')
|
|
1396
|
+
out.name = ment.cmp
|
|
1397
|
+
out.orig = ment.origcmp
|
|
1398
|
+
out.cmpish = true
|
|
1399
|
+
out.pathish = false
|
|
1400
|
+
why.push('cmp-inpath')
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
why.push('path-over-cmp')
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
353
1406
|
|
|
354
|
-
|
|
1407
|
+
else if (
|
|
1408
|
+
'DELETE' === mdesc.method
|
|
1409
|
+
&& null == ment.cmp
|
|
1410
|
+
) {
|
|
1411
|
+
let cmps: { cmp: string, origcmp: string }[] =
|
|
1412
|
+
findcmps(data, mdesc.path, ['responses'], { uniq: true })
|
|
1413
|
+
|
|
1414
|
+
if (1 === cmps.length) {
|
|
1415
|
+
out.name = cmps[0].cmp
|
|
1416
|
+
out.orig = cmps[0].origcmp
|
|
1417
|
+
out.cmpish = true
|
|
1418
|
+
out.pathish = false
|
|
1419
|
+
why.push('cmp-found-delete')
|
|
1420
|
+
}
|
|
1421
|
+
else {
|
|
1422
|
+
why.push('path-primary-delete')
|
|
1423
|
+
}
|
|
355
1424
|
}
|
|
1425
|
+
|
|
356
1426
|
else {
|
|
357
|
-
why.push('
|
|
1427
|
+
why.push('path-primary')
|
|
358
1428
|
}
|
|
359
1429
|
|
|
360
|
-
|
|
1430
|
+
debugpath(mdesc.path, mdesc.method, 'ENTITY-CMP-NAME', mdesc.path,
|
|
1431
|
+
mdesc.method, entname + '->', out, why, ment,
|
|
1432
|
+
IS_ENTCMP_METHOD_RATE, IS_ENTCMP_PATH_RATE)
|
|
1433
|
+
|
|
1434
|
+
// console.log('ECM-Z', out, why, ment)
|
|
1435
|
+
|
|
1436
|
+
return out
|
|
361
1437
|
}
|
|
362
1438
|
|
|
363
1439
|
|
|
1440
|
+
function cmpOccursInPath(data: { def: any, work: any }, cmpname: string): boolean {
|
|
1441
|
+
if (null == data.work.potentialCmpsFromPaths) {
|
|
1442
|
+
data.work.potentialCmpsFromPaths = {}
|
|
1443
|
+
each(data.def.paths, (_pathdef: PathDef, pathstr: string) => {
|
|
1444
|
+
const parts: string[] = data.work.pathmap[pathstr].parts
|
|
1445
|
+
|
|
1446
|
+
parts
|
|
1447
|
+
.filter(p => !p.startsWith('{'))
|
|
1448
|
+
.map(p => data.work.potentialCmpsFromPaths[canonize(p)] = true)
|
|
1449
|
+
})
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
return null != data.work.potentialCmpsFromPaths[cmpname]
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
|
|
364
1459
|
function isListResponse(
|
|
365
|
-
|
|
1460
|
+
mdesc: Record<string, any>,
|
|
366
1461
|
pathStr: string,
|
|
367
|
-
entdesc: EntityDesc,
|
|
368
1462
|
why: string[]
|
|
369
1463
|
): boolean {
|
|
1464
|
+
const ment = mdesc.MethodEntity
|
|
1465
|
+
const pm = ment.pm
|
|
370
1466
|
|
|
371
|
-
const caught = capture(methodDef, {
|
|
372
|
-
responses: {
|
|
373
|
-
'`$ANY`': { content: { 'application/json': { schema: '`$CAPTURE`' } } },
|
|
374
|
-
}
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
const schema = caught.schema
|
|
378
1467
|
let islist = false
|
|
1468
|
+
let schema
|
|
379
1469
|
|
|
380
|
-
if (
|
|
381
|
-
why.push('
|
|
1470
|
+
if (pm && pm.expr.endsWith('p/')) {
|
|
1471
|
+
why.push('end-param')
|
|
382
1472
|
}
|
|
383
1473
|
else {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
1474
|
+
|
|
1475
|
+
let caught = capture(mdesc, {
|
|
1476
|
+
responses:
|
|
1477
|
+
// '`$ANY`': { content: { 'application/json': { schema: '`$CAPTURE`' } } },
|
|
1478
|
+
['`$SELECT`', { '$KEY': { '`$OR`': ['200', '201'] } },
|
|
1479
|
+
{ content: { 'application/json': { schema: '`$CAPTURE`' } } }],
|
|
1480
|
+
|
|
1481
|
+
})
|
|
1482
|
+
|
|
1483
|
+
schema = caught.schema
|
|
1484
|
+
|
|
1485
|
+
if (null == schema) {
|
|
1486
|
+
caught = capture(mdesc, {
|
|
1487
|
+
responses:
|
|
1488
|
+
// '`$ANY`': { content: { 'application/json': { schema: '`$CAPTURE`' } } },
|
|
1489
|
+
['`$SELECT`', { '$KEY': { '`$OR`': ['200', '201'] } },
|
|
1490
|
+
{ schema: '`$CAPTURE`' }],
|
|
1491
|
+
|
|
1492
|
+
})
|
|
1493
|
+
schema = caught.schema
|
|
387
1494
|
}
|
|
388
1495
|
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
1496
|
+
if (null == schema) {
|
|
1497
|
+
why.push('no-schema')
|
|
1498
|
+
}
|
|
1499
|
+
else {
|
|
1500
|
+
if (schema.type === 'array') {
|
|
1501
|
+
why.push('array')
|
|
1502
|
+
islist = true
|
|
1503
|
+
}
|
|
393
1504
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
islist = true
|
|
397
|
-
}
|
|
1505
|
+
if (!islist) {
|
|
1506
|
+
const properties = resolveSchemaProperties(schema)
|
|
398
1507
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
) {
|
|
403
|
-
why.push('two-prop:' + prop.key$)
|
|
1508
|
+
each(properties, (prop) => {
|
|
1509
|
+
if (prop.type === 'array') {
|
|
1510
|
+
why.push('array-prop:' + prop.key$)
|
|
404
1511
|
islist = true
|
|
405
1512
|
}
|
|
1513
|
+
})
|
|
1514
|
+
}
|
|
406
1515
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1516
|
+
if (!islist) {
|
|
1517
|
+
why.push('not-list')
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
411
1521
|
|
|
412
|
-
if (prop.key$ === entdesc.origname) {
|
|
413
|
-
why.push('origname:' + entdesc.origname)
|
|
414
|
-
islist = true
|
|
415
|
-
}
|
|
416
1522
|
|
|
417
|
-
|
|
418
|
-
if (listent === entdesc.name) {
|
|
419
|
-
why.push('listent:' + listent)
|
|
420
|
-
islist = true
|
|
421
|
-
}
|
|
1523
|
+
debugpath(pathStr, mdesc.method, 'IS-LIST', islist, why, schema)
|
|
422
1524
|
|
|
1525
|
+
return islist
|
|
1526
|
+
}
|
|
423
1527
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
1528
|
+
|
|
1529
|
+
function resolveSchemaProperties(schema: any) {
|
|
1530
|
+
let properties: Record<string, any> = {}
|
|
1531
|
+
|
|
1532
|
+
// This is definitely heuristic!
|
|
1533
|
+
if (schema.allOf) {
|
|
1534
|
+
for (let i = schema.allOf.length - 1; -1 < i; --i) {
|
|
1535
|
+
properties = merge([properties, schema.allOf[i].properties || {}])
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
if (schema.properties) {
|
|
1540
|
+
properties = merge([properties, schema.properties])
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
return properties
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
function updateAction(
|
|
1554
|
+
methodName: string,
|
|
1555
|
+
oldParam: string,
|
|
1556
|
+
actionName: string,
|
|
1557
|
+
entityDesc: EntityDesc,
|
|
1558
|
+
pathdesc: EntityPathDesc,
|
|
1559
|
+
why: string
|
|
1560
|
+
) {
|
|
1561
|
+
if (
|
|
1562
|
+
// Entity not already encoding action.
|
|
1563
|
+
!entityDesc.name.endsWith(canonize(actionName))
|
|
1564
|
+
&& null == pathdesc.action[actionName]
|
|
1565
|
+
) {
|
|
1566
|
+
pathdesc.action[actionName] = {
|
|
1567
|
+
// kind: '`$BOOLEAN`',
|
|
1568
|
+
why_action: ['ent', `${entityDesc.name}`, `${why}`, `${oldParam}`, `${methodName}`]
|
|
429
1569
|
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
430
1573
|
|
|
431
|
-
|
|
432
|
-
|
|
1574
|
+
|
|
1575
|
+
function updateParamRename(
|
|
1576
|
+
ctx: ApiDefContext,
|
|
1577
|
+
data: { def: any, guide: any, work: any },
|
|
1578
|
+
path: string,
|
|
1579
|
+
method: string,
|
|
1580
|
+
paramRenameCapture: {
|
|
1581
|
+
rename: Record<string, string>,
|
|
1582
|
+
why: Record<string, string[]>,
|
|
1583
|
+
},
|
|
1584
|
+
oldParamName: string,
|
|
1585
|
+
newParamName: string,
|
|
1586
|
+
why: string,
|
|
1587
|
+
) {
|
|
1588
|
+
const existingNewName = paramRenameCapture.rename[oldParamName]
|
|
1589
|
+
const existingWhy = paramRenameCapture.why[oldParamName]
|
|
1590
|
+
|
|
1591
|
+
debugpath(path, method, 'UPDATE-PARAM-RENAME', path, oldParamName, newParamName, existingNewName)
|
|
1592
|
+
|
|
1593
|
+
if (null == existingNewName) {
|
|
1594
|
+
paramRenameCapture.rename[oldParamName] = newParamName
|
|
1595
|
+
if (!existingWhy.includes(why)) {
|
|
1596
|
+
existingWhy.push(why)
|
|
433
1597
|
}
|
|
1598
|
+
}
|
|
1599
|
+
else if (newParamName == existingNewName) {
|
|
1600
|
+
// if (!existingWhy.includes(why)) {
|
|
1601
|
+
// existingWhy.push(why)
|
|
434
1602
|
// }
|
|
435
1603
|
}
|
|
1604
|
+
else {
|
|
1605
|
+
ctx.warn({
|
|
1606
|
+
paramRenameCapture, oldParamName, newParamName, why,
|
|
1607
|
+
note: 'Param rename mismatch: existing: ' +
|
|
1608
|
+
oldParamName + ' -> ' + existingNewName + ' (why: ' + existingNewName + ') ' +
|
|
1609
|
+
' proposed: ' + newParamName + ' (why: ' + why + ') ' +
|
|
1610
|
+
'for path: ' + path + '. method: ' + method
|
|
1611
|
+
})
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
436
1614
|
|
|
437
|
-
|
|
1615
|
+
|
|
1616
|
+
|
|
1617
|
+
function isParam(partStr: string) {
|
|
1618
|
+
return '{' === partStr[0] && '}' === partStr[partStr.length - 1]
|
|
438
1619
|
}
|
|
439
1620
|
|
|
440
1621
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
1622
|
+
/*
|
|
1623
|
+
function fixEntName(origName: string) {
|
|
1624
|
+
if (null == origName) {
|
|
1625
|
+
return origName
|
|
1626
|
+
}
|
|
1627
|
+
return depluralize(snakify(origName))
|
|
1628
|
+
}
|
|
1629
|
+
*/
|
|
1630
|
+
|
|
1631
|
+
|
|
1632
|
+
function findcmps(
|
|
1633
|
+
data: { def: any, work: any, guide: any },
|
|
1634
|
+
pathStr: string,
|
|
1635
|
+
underprops: string[],
|
|
1636
|
+
opts?: { uniq?: boolean }
|
|
1637
|
+
): { cmp: string, origcmp: string }[] {
|
|
1638
|
+
const cmplist: string[] = []
|
|
1639
|
+
const cmpset = new Set<string>()
|
|
1640
|
+
|
|
1641
|
+
// TODO: cache in ctx.work
|
|
1642
|
+
|
|
1643
|
+
each(data.def.paths[pathStr])
|
|
1644
|
+
.map((md: MethodDef) => {
|
|
1645
|
+
underprops.map((up: string) => {
|
|
1646
|
+
let found = find((md as any)[up], 'x-ref')
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
found.map((xref: { val: string }) => {
|
|
1650
|
+
// console.log('FINDCMPS', pathStr, (md as any).key$, up, xref.val)
|
|
1651
|
+
let m = xref.val.match(/\/(components\/schemas|definitions)\/(.+)$/)
|
|
1652
|
+
if (m) {
|
|
1653
|
+
cmplist.push(m[2])
|
|
1654
|
+
cmpset.add(m[2])
|
|
1655
|
+
}
|
|
1656
|
+
})
|
|
1657
|
+
})
|
|
1658
|
+
})
|
|
1659
|
+
// console.log('FOUNDCMPS', cmps)
|
|
1660
|
+
return (opts?.uniq ? Array.from(cmpset) : cmplist).map(n =>
|
|
1661
|
+
({ cmp: canonize(n), origcmp: n }))
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
|
|
1665
|
+
function makeMethodEntityDesc(desc: Record<string, any>): MethodEntityDesc {
|
|
1666
|
+
let ment: MethodEntityDesc = {
|
|
1667
|
+
cmp: desc.cmp ?? null,
|
|
1668
|
+
origcmp: desc.origcmp ?? null,
|
|
1669
|
+
origcmpref: desc.origcmpref ?? null,
|
|
1670
|
+
|
|
1671
|
+
ref: desc.ref ?? '',
|
|
1672
|
+
why_cmp: desc.why_cmp ?? [],
|
|
1673
|
+
cmpoccur: desc.cmpoccur ?? 0,
|
|
1674
|
+
path_rate: desc.path_rate ?? 0,
|
|
1675
|
+
method_rate: desc.method_rate ?? 0,
|
|
1676
|
+
entname: desc.entname ?? '',
|
|
1677
|
+
why_op: desc.why_op ?? [],
|
|
1678
|
+
rename: desc.rename ?? { param: {} },
|
|
1679
|
+
why_rename: desc.why_rename ?? { why_param: {} },
|
|
1680
|
+
rename_orig: desc.rename_orig ?? [],
|
|
1681
|
+
opname: desc.opname ?? '',
|
|
1682
|
+
why_opname: desc.why_opname ?? [],
|
|
1683
|
+
}
|
|
1684
|
+
return ment
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
|
|
1688
|
+
function findPotentialSchemaRefs(pathStr: string, methodName: string, responses: any) {
|
|
1689
|
+
const xrefs: string[] = []
|
|
1690
|
+
const rescodes = ['200', '201']
|
|
1691
|
+
for (let rescode of rescodes) {
|
|
1692
|
+
const schema = getResponseSchema(responses[rescode])
|
|
1693
|
+
if (null != schema) {
|
|
1694
|
+
if (null != schema['x-ref']) {
|
|
1695
|
+
xrefs.push(schema['x-ref'])
|
|
1696
|
+
}
|
|
1697
|
+
else if ('array' === schema.type && null != schema.items?.['x-ref']) {
|
|
1698
|
+
xrefs.push(schema.items?.['x-ref'])
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
446
1701
|
}
|
|
1702
|
+
|
|
1703
|
+
debugpath(pathStr, methodName, 'POTENTIAL-SCHEMA-REFS', xrefs)
|
|
1704
|
+
return xrefs
|
|
447
1705
|
}
|
|
448
1706
|
|
|
449
1707
|
|
|
1708
|
+
function hasMethod(def: any, pathStr: string, methodName: string) {
|
|
1709
|
+
const pathDef = def?.paths?.[pathStr]
|
|
1710
|
+
const found = (
|
|
1711
|
+
null != pathDef
|
|
1712
|
+
&& (
|
|
1713
|
+
null != pathDef[methodName.toLowerCase()]
|
|
1714
|
+
|| null != pathDef[methodName.toUpperCase()]
|
|
1715
|
+
)
|
|
1716
|
+
)
|
|
1717
|
+
console.log('hasMethod', pathStr, methodName, found)
|
|
1718
|
+
return found
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
/*
|
|
1724
|
+
// Some decisions require the full list of potential entities.
|
|
1725
|
+
function reviewEntityDescs(ctx: ApiDefContext, result: any) {
|
|
1726
|
+
const guide: Guide = result.data.guide
|
|
1727
|
+
const metrics = guide.metrics
|
|
1728
|
+
const entityDescs = result.data.work.entmap
|
|
1729
|
+
|
|
1730
|
+
if (0 < metrics.count.cmp) {
|
|
1731
|
+
items(entityDescs).map(([entname, entdesc]: [string, EntityDesc]) => {
|
|
1732
|
+
|
|
1733
|
+
// Entities with a single path and single op and no cmp are suspicious
|
|
1734
|
+
|
|
1735
|
+
const pathmap = entdesc.path
|
|
1736
|
+
const pathcount = size(pathmap)
|
|
1737
|
+
const hascmp = null != entdesc.cmp?.namedesc
|
|
1738
|
+
|
|
1739
|
+
if (
|
|
1740
|
+
1 === pathcount
|
|
1741
|
+
&& !hascmp
|
|
1742
|
+
) {
|
|
1743
|
+
let pathdesc: EntityPathDesc = each(pathmap)[0]
|
|
1744
|
+
const pathStr = (pathdesc as any).key$
|
|
1745
|
+
|
|
1746
|
+
if (1 === size(pathdesc.op)) {
|
|
1747
|
+
let op = pathdesc.op
|
|
1748
|
+
|
|
1749
|
+
// console.log('REVIEW', entdesc.name, pathcount, hascmp, op)
|
|
1750
|
+
|
|
1751
|
+
if (op.create) {
|
|
1752
|
+
|
|
1753
|
+
// Entities without "good" components
|
|
1754
|
+
if (
|
|
1755
|
+
entname.includes('_')
|
|
1756
|
+
&& pathdesc.pm.expr.endsWith('p/t/')
|
|
1757
|
+
) {
|
|
1758
|
+
const lastpart = canonize(getelem(pathdesc.pm, -1))
|
|
1759
|
+
const tgtent = entityDescs[lastpart]
|
|
1760
|
+
|
|
1761
|
+
// console.log('REVIEW', entname, entdesc.cmp, size(pathmap), lastpart, realent)
|
|
1762
|
+
|
|
1763
|
+
if (
|
|
1764
|
+
null != tgtent
|
|
1765
|
+
&& tgtent.name !== entname
|
|
1766
|
+
&& (
|
|
1767
|
+
null == tgtent.cmp
|
|
1768
|
+
|| lastpart == tgtent.cmp
|
|
1769
|
+
)
|
|
1770
|
+
) {
|
|
1771
|
+
|
|
1772
|
+
// Actually a known component
|
|
1773
|
+
// console.dir(entdesc, { depth: null })
|
|
1774
|
+
|
|
1775
|
+
|
|
1776
|
+
const realent = guide.entity[entname]
|
|
1777
|
+
const realpathmap = realent.path
|
|
1778
|
+
let realpath = realpathmap[pathStr]
|
|
1779
|
+
|
|
1780
|
+
if (null == realpath) {
|
|
1781
|
+
realpath = realpathmap[pathStr] = pathdesc
|
|
1782
|
+
}
|
|
1783
|
+
else if (null == realpath.op?.create) {
|
|
1784
|
+
realpath.op = (realpath.op ?? {})
|
|
1785
|
+
realpath.op.create = pathdesc.op.create
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
realpath.op.create.why_op =
|
|
1789
|
+
'was/create/A:' + entname + ':' + realpath.op.create.why_op
|
|
1790
|
+
|
|
1791
|
+
delete entityDescs[entname]
|
|
1792
|
+
|
|
1793
|
+
// console.log('REPLACE', entname, realent.name, realpath)
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
else if (op.remove) {
|
|
1800
|
+
const otherents: EntityDesc[] = each(entityDescs)
|
|
1801
|
+
.filter((ed: EntityDesc) => ed !== entdesc && each(ed.path)
|
|
1802
|
+
.filter(epd => epd.key$ === pathStr).length)
|
|
1803
|
+
|
|
1804
|
+
const otherent = 1 === otherents.length ? otherents[0] : null
|
|
1805
|
+
|
|
1806
|
+
// console.log('OTHERENT', pathStr, otherents.length, otherent)
|
|
1807
|
+
|
|
1808
|
+
if (null != otherent && null != otherent.cmp) {
|
|
1809
|
+
const otherpath = otherent.path[pathStr]
|
|
1810
|
+
|
|
1811
|
+
if (null == otherpath.op.remove) {
|
|
1812
|
+
otherpath.op.remove = op.remove
|
|
1813
|
+
otherpath.op.remove.why_op =
|
|
1814
|
+
'was/delete/A:' + entname + ':' + op.remove.why_op
|
|
1815
|
+
delete entityDescs[entname]
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
debugpath(pathdesc.pm.path, null,
|
|
1821
|
+
'REVIEW-ENTITY', formatJSONIC(entdesc, { hsepd: 0, $: true, color: true }))
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
})
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
*/
|
|
1830
|
+
|
|
450
1831
|
|
|
451
1832
|
export {
|
|
452
1833
|
heuristic01
|