@voxgig/apidef 2.4.1 → 3.0.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/dist/apidef.d.ts +5 -1
- package/dist/apidef.js +197 -112
- 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 +29 -4
- 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 +89 -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 +1120 -234
- package/dist/guide/heuristic01.js.map +1 -1
- package/dist/model.d.ts +55 -0
- package/dist/model.js +4 -0
- package/dist/model.js.map +1 -0
- package/dist/parse.d.ts +1 -2
- package/dist/parse.js +8 -47
- package/dist/parse.js.map +1 -1
- package/dist/transform/args.d.ts +3 -0
- package/dist/transform/args.js +58 -0
- package/dist/transform/args.js.map +1 -0
- package/dist/transform/clean.js +27 -3
- package/dist/transform/clean.js.map +1 -1
- package/dist/transform/entity.d.ts +11 -3
- package/dist/transform/entity.js +57 -41
- package/dist/transform/entity.js.map +1 -1
- package/dist/transform/field.d.ts +3 -3
- package/dist/transform/field.js +90 -65
- package/dist/transform/field.js.map +1 -1
- package/dist/transform/operation.d.ts +1 -1
- package/dist/transform/operation.js +94 -296
- package/dist/transform/operation.js.map +1 -1
- package/dist/transform/select.d.ts +3 -0
- package/dist/transform/select.js +44 -0
- package/dist/transform/select.js.map +1 -0
- package/dist/transform/top.d.ts +9 -0
- package/dist/transform/top.js +11 -2
- package/dist/transform/top.js.map +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 +112 -19
- package/dist/types.js +4 -2
- package/dist/types.js.map +1 -1
- package/dist/utility.d.ts +30 -2
- package/dist/utility.js +381 -6
- package/dist/utility.js.map +1 -1
- package/model/apidef.jsonic +75 -1
- package/model/guide.jsonic +14 -44
- package/package.json +17 -14
- package/src/apidef.ts +264 -121
- 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 +39 -5
- package/src/def.ts +91 -0
- package/src/desc.ts +143 -0
- package/src/guide/guide.ts +207 -134
- package/src/guide/heuristic01.ts +1651 -272
- package/src/model.ts +98 -0
- package/src/parse.ts +5 -61
- package/src/schematron.ts.off +317 -0
- package/src/transform/args.ts +102 -0
- package/src/transform/clean.ts +43 -8
- package/src/transform/entity.ts +100 -51
- package/src/transform/field.ts +150 -71
- package/src/transform/operation.ts +118 -414
- package/src/transform/select.ts +90 -0
- package/src/transform/top.ts +76 -3
- package/src/transform.ts +4 -0
- package/src/types.ts +185 -5
- package/src/utility.ts +481 -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,1544 @@ 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
|
+
const transform: Record<string, any> = {
|
|
1030
|
+
req: undefined,
|
|
1031
|
+
res: undefined,
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const resokdef = mdesc.responses?.[200] || mdesc.responses?.[201]
|
|
1035
|
+
const resprops = getResponseSchema(resokdef)?.properties
|
|
1036
|
+
debugpath(pathStr, methodName, 'TRANSFORM-RES', keysof(resprops))
|
|
1037
|
+
|
|
1038
|
+
if (resprops) {
|
|
1039
|
+
if (resprops[entdesc.origname]) {
|
|
1040
|
+
transform.res = '`body.' + entdesc.origname + '`'
|
|
236
1041
|
}
|
|
237
|
-
else {
|
|
238
|
-
|
|
239
|
-
why_name.push('path:' + m[1])
|
|
1042
|
+
else if (resprops[entdesc.name]) {
|
|
1043
|
+
transform.res = '`body.' + entdesc.name + '`'
|
|
240
1044
|
}
|
|
1045
|
+
}
|
|
241
1046
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
1047
|
+
const reqprops = getRequestBodySchema(mdesc.requestBody)
|
|
1048
|
+
debugpath(pathStr, methodName, 'TRANSFORM-REQ', keysof(reqprops))
|
|
1049
|
+
if (reqprops) {
|
|
1050
|
+
if (reqprops[entdesc.origname]) {
|
|
1051
|
+
transform.req = { [entdesc.origname]: '`reqdata`' }
|
|
1052
|
+
}
|
|
1053
|
+
else if (reqprops[entdesc.origname]) {
|
|
1054
|
+
transform.req = { [entdesc.origname]: '`reqdata`' }
|
|
1055
|
+
}
|
|
247
1056
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (!isempty(transform)) {
|
|
1060
|
+
op[opname].transform = transform
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
function BuildEntity(spec: TaskSpec) {
|
|
1067
|
+
const entdesc = spec.node.val
|
|
1068
|
+
// console.log('BUILD-ENTITY')
|
|
1069
|
+
// console.dir(entdesc, { depth: null })
|
|
1070
|
+
|
|
1071
|
+
const guide: Guide = spec.data.guide
|
|
1072
|
+
guide.metrics.count.entity++
|
|
1073
|
+
|
|
1074
|
+
const entityMap: Record<string, GuideEntity> = guide.entity
|
|
1075
|
+
|
|
1076
|
+
const path: Record<string, GuidePath> = {}
|
|
1077
|
+
|
|
1078
|
+
const rename_param = (pathdesc: any) => {
|
|
1079
|
+
// console.log('RENAME-PATHDESC', pathdesc)
|
|
1080
|
+
const out: Record<string, GuideRenameParam> = {}
|
|
1081
|
+
each(pathdesc.rename.param, (item: any) => {
|
|
1082
|
+
out[item.key$] = {
|
|
1083
|
+
target: item.val$,
|
|
1084
|
+
why_rename: pathdesc.why_rename.why_param[item.key$]
|
|
253
1085
|
}
|
|
1086
|
+
})
|
|
1087
|
+
return out
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
each(entdesc.path, (pathdesc: any, pathstr: string) => {
|
|
1091
|
+
const guidepath = {
|
|
1092
|
+
why_path: pathdesc.why_path,
|
|
1093
|
+
action: pathdesc.action,
|
|
1094
|
+
rename: {
|
|
1095
|
+
param: rename_param(pathdesc)
|
|
1096
|
+
},
|
|
1097
|
+
op: pathdesc.op
|
|
254
1098
|
}
|
|
1099
|
+
path[pathstr] = guidepath
|
|
1100
|
+
})
|
|
1101
|
+
|
|
1102
|
+
entityMap[entdesc.name] = {
|
|
1103
|
+
name: entdesc.name,
|
|
1104
|
+
orig: entdesc.origcmp,
|
|
1105
|
+
path,
|
|
255
1106
|
}
|
|
256
1107
|
|
|
257
|
-
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
function entityPathMatch_tpte(
|
|
1117
|
+
data: { def: any, guide: any, work: any },
|
|
1118
|
+
pm: PathMatch,
|
|
1119
|
+
mdesc: any,
|
|
1120
|
+
why: string[]
|
|
1121
|
+
) {
|
|
1122
|
+
const ment = mdesc.MethodEntity
|
|
1123
|
+
|
|
1124
|
+
const pathNameIndex = 2
|
|
1125
|
+
|
|
1126
|
+
why.push('path=t/p/t/')
|
|
1127
|
+
const origPathName = pm[pathNameIndex]
|
|
1128
|
+
let entname = canonize(origPathName)
|
|
1129
|
+
let ecm = undefined
|
|
1130
|
+
|
|
1131
|
+
if (null != ment.cmp) {
|
|
1132
|
+
ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1133
|
+
entname = ecm.name
|
|
1134
|
+
why.push('has-cmp=' + ecm.orig)
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
else if (probableEntityMethod(data, ment, pm, why)) {
|
|
1138
|
+
ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1139
|
+
if (ecm.cmpish) {
|
|
1140
|
+
entname = ecm.name
|
|
1141
|
+
why.push('prob-ent=' + ecm.orig)
|
|
1142
|
+
}
|
|
1143
|
+
else if (endsWithCmp(data, pm)) {
|
|
1144
|
+
entname = canonize(getelem(pm, -1))
|
|
1145
|
+
why.push('prob-ent-last=' + ecm.orig)
|
|
1146
|
+
}
|
|
1147
|
+
else if (0 < findPathsWithPrefix(data, pm.path, { strict: true })) {
|
|
1148
|
+
entname = canonize(getelem(pm, -1))
|
|
1149
|
+
why.push('prob-ent-prefix=' + ecm.orig)
|
|
1150
|
+
}
|
|
1151
|
+
else {
|
|
1152
|
+
entname = canonize(getelem(pm, -3)) + '_' + entname
|
|
1153
|
+
why.push('prob-ent-part')
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Probably an entity action suffix
|
|
258
1158
|
else {
|
|
259
|
-
|
|
260
|
-
|
|
1159
|
+
why.push('prob-ent-act')
|
|
1160
|
+
entname = canonize(getelem(pm, -3))
|
|
261
1161
|
}
|
|
262
1162
|
|
|
1163
|
+
return entname
|
|
1164
|
+
}
|
|
263
1165
|
|
|
264
|
-
|
|
265
|
-
|
|
1166
|
+
function endsWithCmp(data: any, pm: PathMatch) {
|
|
1167
|
+
const last = canonize(getelem(pm, -1))
|
|
1168
|
+
return isOrigCmp(data, last)
|
|
1169
|
+
}
|
|
266
1170
|
|
|
267
|
-
names(entdesc, entname)
|
|
268
1171
|
|
|
269
|
-
|
|
1172
|
+
function isOrigCmp(data: any, name: string) {
|
|
1173
|
+
return null != data.metrics.count.origcmprefs[name]
|
|
1174
|
+
}
|
|
270
1175
|
|
|
271
|
-
entdesc.path = (entdesc.path || {})
|
|
272
|
-
entdesc.path[pathStr] = entdesc.path[pathStr] || {}
|
|
273
|
-
entdesc.path[pathStr].op = entdesc.path[pathStr].op || {}
|
|
274
1176
|
|
|
275
|
-
|
|
276
|
-
|
|
1177
|
+
function entityOccursInPath(parts: string[], entname: string) {
|
|
1178
|
+
let partsLower = parts.map(p => p.toLowerCase())
|
|
1179
|
+
partsLower = partsLower.filter(p => '{' !== p[0]).map(p => canonize(p))
|
|
1180
|
+
return !partsLower.reduce((a: boolean, p: string) => (a && p !== entname), true)
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
function entityPathMatch_tpe(
|
|
1185
|
+
data: { def: any, guide: any, work: any },
|
|
1186
|
+
pm: PathMatch, mdesc: any, why: string[]
|
|
1187
|
+
) {
|
|
1188
|
+
const ment = mdesc.MethodEntity
|
|
1189
|
+
const pathNameIndex = 0
|
|
1190
|
+
|
|
1191
|
+
why.push('path=t/p/')
|
|
1192
|
+
const origPathName = pm[pathNameIndex]
|
|
1193
|
+
// let entname = fixEntName(origPathName)
|
|
1194
|
+
let entname = canonize(origPathName)
|
|
1195
|
+
|
|
1196
|
+
if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
|
|
1197
|
+
let ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1198
|
+
entname = ecm.name
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
why.push('ent-act')
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
return entname
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
function entityPathMatch_pte(
|
|
1209
|
+
data: { def: any, guide: any, work: any },
|
|
1210
|
+
pm: PathMatch, mdesc: any, why: string[]
|
|
1211
|
+
) {
|
|
1212
|
+
const ment = mdesc.MethodEntity
|
|
1213
|
+
const pathNameIndex = 1
|
|
1214
|
+
|
|
1215
|
+
why.push('path=p/t/')
|
|
1216
|
+
const origPathName = pm[pathNameIndex]
|
|
1217
|
+
let entname = canonize(origPathName)
|
|
1218
|
+
|
|
1219
|
+
if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
|
|
1220
|
+
let ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1221
|
+
entname = ecm.name
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
why.push('ent-act')
|
|
277
1225
|
}
|
|
278
1226
|
|
|
279
|
-
return
|
|
1227
|
+
return entname
|
|
280
1228
|
}
|
|
281
1229
|
|
|
282
1230
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
1231
|
+
function entityPathMatch_te(
|
|
1232
|
+
data: { def: any, guide: any, work: any },
|
|
1233
|
+
pm: PathMatch, mdesc: any, why: string[]
|
|
1234
|
+
) {
|
|
1235
|
+
const ment = mdesc.MethodEntity
|
|
1236
|
+
const pathNameIndex = 0
|
|
1237
|
+
|
|
1238
|
+
why.push('path=t/')
|
|
1239
|
+
const origPathName = pm[pathNameIndex]
|
|
1240
|
+
// let entname = fixEntName(origPathName)
|
|
1241
|
+
let entname = canonize(origPathName)
|
|
1242
|
+
|
|
1243
|
+
if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
|
|
1244
|
+
let ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1245
|
+
entname = ecm.name
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
why.push('ent-act')
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
return entname
|
|
288
1252
|
}
|
|
289
1253
|
|
|
290
1254
|
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
): string | undefined {
|
|
298
|
-
let compname: string | undefined = undefined
|
|
1255
|
+
function entityPathMatch_tpp(
|
|
1256
|
+
data: { def: any, guide: any, work: any },
|
|
1257
|
+
pm: PathMatch, mdesc: any, why: string[]
|
|
1258
|
+
) {
|
|
1259
|
+
const ment = mdesc.MethodEntity
|
|
1260
|
+
const pathNameIndex = 0
|
|
299
1261
|
|
|
300
|
-
|
|
301
|
-
|
|
1262
|
+
why.push('path=t/p/p')
|
|
1263
|
+
const origPathName = pm[pathNameIndex]
|
|
1264
|
+
// let entname = fixEntName(origPathName)
|
|
1265
|
+
let entname = canonize(origPathName)
|
|
302
1266
|
|
|
303
|
-
|
|
304
|
-
|
|
1267
|
+
if (null != ment.cmp || probableEntityMethod(data, mdesc, pm, why)) {
|
|
1268
|
+
let ecm = entityCmpMatch(data, entname, mdesc, why)
|
|
1269
|
+
entname = ecm.name
|
|
1270
|
+
}
|
|
1271
|
+
else {
|
|
1272
|
+
why.push('ent-act')
|
|
1273
|
+
}
|
|
305
1274
|
|
|
306
|
-
|
|
1275
|
+
return entname
|
|
1276
|
+
}
|
|
307
1277
|
|
|
308
|
-
// console.log('RCN', pathStr, methodStr, xrefs.map(x => [x.val, x.path.length]))
|
|
309
1278
|
|
|
310
|
-
|
|
1279
|
+
function getRequestBodySchema(requestBody: any) {
|
|
1280
|
+
return requestBody?.content?.['application/json']?.schema ??
|
|
1281
|
+
requestBody?.schema
|
|
1282
|
+
}
|
|
311
1283
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
1284
|
+
function getResponseSchema(response: any) {
|
|
1285
|
+
return response?.content?.['application/json']?.schema ??
|
|
1286
|
+
response?.schema
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
// No entity component was found, but there still might be an entity.
|
|
1291
|
+
function probableEntityMethod(
|
|
1292
|
+
data: { def: any },
|
|
1293
|
+
mdesc: any,
|
|
1294
|
+
pm: PathMatch,
|
|
1295
|
+
why: string[]
|
|
1296
|
+
) {
|
|
1297
|
+
const request = mdesc.requestBody
|
|
1298
|
+
const reqSchema = getRequestBodySchema(request)
|
|
1299
|
+
|
|
1300
|
+
const response = mdesc.responses?.['201'] || mdesc.responses?.['200']
|
|
1301
|
+
const resSchema = getResponseSchema(response)
|
|
1302
|
+
const noResponse = null == resSchema && null != mdesc.responses?.['204']
|
|
1303
|
+
|
|
1304
|
+
let prob_why = ''
|
|
1305
|
+
|
|
1306
|
+
let probent = false
|
|
1307
|
+
|
|
1308
|
+
if (noResponse) {
|
|
1309
|
+
// No response at all means not an action, thus probably an entity.
|
|
1310
|
+
prob_why = 'nores'
|
|
1311
|
+
probent = true
|
|
318
1312
|
}
|
|
319
1313
|
|
|
320
|
-
if (null !=
|
|
321
|
-
|
|
1314
|
+
else if (null != reqSchema) {
|
|
1315
|
+
if (
|
|
1316
|
+
'POST' === mdesc.method
|
|
1317
|
+
&& !pm.expr.endsWith('/p/')
|
|
1318
|
+
|
|
1319
|
+
// A real entity would probably occur in at least one other t/p path
|
|
1320
|
+
// otherwise this is probably an action
|
|
1321
|
+
&& (1 < Object.keys(data.def.paths).filter(path =>
|
|
1322
|
+
path.includes('/' + pm[pm.length - 1] + '/')).length)
|
|
1323
|
+
) {
|
|
1324
|
+
prob_why = 'post'
|
|
1325
|
+
probent = true
|
|
1326
|
+
}
|
|
322
1327
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
1328
|
+
else if (
|
|
1329
|
+
('PUT' === mdesc.method || 'PATCH' === mdesc.method)
|
|
1330
|
+
&& pm.expr.endsWith('/p/')
|
|
1331
|
+
) {
|
|
1332
|
+
prob_why = 'putish'
|
|
1333
|
+
probent = true
|
|
326
1334
|
}
|
|
327
1335
|
}
|
|
1336
|
+
else if ('GET' === mdesc.method) {
|
|
1337
|
+
prob_why = 'get'
|
|
1338
|
+
probent = true
|
|
1339
|
+
}
|
|
328
1340
|
|
|
329
|
-
|
|
330
|
-
}
|
|
1341
|
+
const rescodes = Object.keys(mdesc.responses ?? {})
|
|
331
1342
|
|
|
1343
|
+
debugpath(mdesc.path, mdesc.method, 'PROBABLE-ENTITY-RESPONSE',
|
|
1344
|
+
{ mdesc, responses: rescodes, probent, prob_why })
|
|
332
1345
|
|
|
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)
|
|
1346
|
+
why.push('entres=' + probent + '/' + rescodes + ('' === prob_why ? '' : '/' + prob_why))
|
|
342
1347
|
|
|
1348
|
+
return probent
|
|
1349
|
+
}
|
|
343
1350
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
1351
|
+
|
|
1352
|
+
function entityCmpMatch(
|
|
1353
|
+
data: { def: any, guide: any, work: any },
|
|
1354
|
+
entname: string,
|
|
1355
|
+
mdesc: any,
|
|
1356
|
+
why: string[]
|
|
1357
|
+
): {
|
|
1358
|
+
name: string,
|
|
1359
|
+
orig: string,
|
|
1360
|
+
cmpish: boolean,
|
|
1361
|
+
pathish: boolean,
|
|
1362
|
+
} {
|
|
1363
|
+
const ment = mdesc.MethodEntity
|
|
1364
|
+
|
|
1365
|
+
let out = {
|
|
1366
|
+
name: entname,
|
|
1367
|
+
orig: ment.origcmp ?? entname,
|
|
1368
|
+
cmpish: false,
|
|
1369
|
+
pathish: true,
|
|
348
1370
|
}
|
|
349
1371
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
1372
|
+
// console.log('ECM-A', out, ment)
|
|
1373
|
+
|
|
1374
|
+
const cmpInfrequent = (
|
|
1375
|
+
ment.method_rate < IS_ENTCMP_METHOD_RATE
|
|
1376
|
+
|| ment.path_rate < IS_ENTCMP_PATH_RATE
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1379
|
+
if (
|
|
1380
|
+
null != ment.cmp
|
|
1381
|
+
&& entname != ment.cmp
|
|
1382
|
+
&& !ment.cmp.startsWith(entname)
|
|
1383
|
+
) {
|
|
1384
|
+
if (cmpInfrequent) {
|
|
1385
|
+
why.push('cmp-primary')
|
|
1386
|
+
out.name = ment.cmp
|
|
1387
|
+
out.orig = ment.origcmp
|
|
1388
|
+
out.cmpish = true
|
|
1389
|
+
out.pathish = false
|
|
1390
|
+
why.push('cmp-infreq')
|
|
1391
|
+
}
|
|
1392
|
+
else if (cmpOccursInPath(data, ment.cmp)) {
|
|
1393
|
+
why.push('cmp-path')
|
|
1394
|
+
out.name = ment.cmp
|
|
1395
|
+
out.orig = ment.origcmp
|
|
1396
|
+
out.cmpish = true
|
|
1397
|
+
out.pathish = false
|
|
1398
|
+
why.push('cmp-inpath')
|
|
1399
|
+
}
|
|
1400
|
+
else {
|
|
1401
|
+
why.push('path-over-cmp')
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
353
1404
|
|
|
354
|
-
|
|
1405
|
+
else if (
|
|
1406
|
+
'DELETE' === mdesc.method
|
|
1407
|
+
&& null == ment.cmp
|
|
1408
|
+
) {
|
|
1409
|
+
let cmps: { cmp: string, origcmp: string }[] =
|
|
1410
|
+
findcmps(data, mdesc.path, ['responses'], { uniq: true })
|
|
1411
|
+
|
|
1412
|
+
if (1 === cmps.length) {
|
|
1413
|
+
out.name = cmps[0].cmp
|
|
1414
|
+
out.orig = cmps[0].origcmp
|
|
1415
|
+
out.cmpish = true
|
|
1416
|
+
out.pathish = false
|
|
1417
|
+
why.push('cmp-found-delete')
|
|
1418
|
+
}
|
|
1419
|
+
else {
|
|
1420
|
+
why.push('path-primary-delete')
|
|
1421
|
+
}
|
|
355
1422
|
}
|
|
1423
|
+
|
|
356
1424
|
else {
|
|
357
|
-
why.push('
|
|
1425
|
+
why.push('path-primary')
|
|
358
1426
|
}
|
|
359
1427
|
|
|
360
|
-
|
|
1428
|
+
debugpath(mdesc.path, mdesc.method, 'ENTITY-CMP-NAME', mdesc.path,
|
|
1429
|
+
mdesc.method, entname + '->', out, why, ment,
|
|
1430
|
+
IS_ENTCMP_METHOD_RATE, IS_ENTCMP_PATH_RATE)
|
|
1431
|
+
|
|
1432
|
+
// console.log('ECM-Z', out, why, ment)
|
|
1433
|
+
|
|
1434
|
+
return out
|
|
361
1435
|
}
|
|
362
1436
|
|
|
363
1437
|
|
|
1438
|
+
function cmpOccursInPath(data: { def: any, work: any }, cmpname: string): boolean {
|
|
1439
|
+
if (null == data.work.potentialCmpsFromPaths) {
|
|
1440
|
+
data.work.potentialCmpsFromPaths = {}
|
|
1441
|
+
each(data.def.paths, (_pathdef: PathDef, pathstr: string) => {
|
|
1442
|
+
const parts: string[] = data.work.pathmap[pathstr].parts
|
|
1443
|
+
|
|
1444
|
+
parts
|
|
1445
|
+
.filter(p => !p.startsWith('{'))
|
|
1446
|
+
.map(p => data.work.potentialCmpsFromPaths[canonize(p)] = true)
|
|
1447
|
+
})
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
return null != data.work.potentialCmpsFromPaths[cmpname]
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
|
|
364
1457
|
function isListResponse(
|
|
365
|
-
|
|
1458
|
+
mdesc: Record<string, any>,
|
|
366
1459
|
pathStr: string,
|
|
367
|
-
entdesc: EntityDesc,
|
|
368
1460
|
why: string[]
|
|
369
1461
|
): boolean {
|
|
1462
|
+
const ment = mdesc.MethodEntity
|
|
1463
|
+
const pm = ment.pm
|
|
370
1464
|
|
|
371
|
-
const caught = capture(methodDef, {
|
|
372
|
-
responses: {
|
|
373
|
-
'`$ANY`': { content: { 'application/json': { schema: '`$CAPTURE`' } } },
|
|
374
|
-
}
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
const schema = caught.schema
|
|
378
1465
|
let islist = false
|
|
1466
|
+
let schema
|
|
379
1467
|
|
|
380
|
-
if (
|
|
381
|
-
why.push('
|
|
1468
|
+
if (pm && pm.expr.endsWith('p/')) {
|
|
1469
|
+
why.push('end-param')
|
|
382
1470
|
}
|
|
383
1471
|
else {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
1472
|
+
|
|
1473
|
+
let caught = capture(mdesc, {
|
|
1474
|
+
responses:
|
|
1475
|
+
// '`$ANY`': { content: { 'application/json': { schema: '`$CAPTURE`' } } },
|
|
1476
|
+
['`$SELECT`', { '$KEY': { '`$OR`': ['200', '201'] } },
|
|
1477
|
+
{ content: { 'application/json': { schema: '`$CAPTURE`' } } }],
|
|
1478
|
+
|
|
1479
|
+
})
|
|
1480
|
+
|
|
1481
|
+
schema = caught.schema
|
|
1482
|
+
|
|
1483
|
+
if (null == schema) {
|
|
1484
|
+
caught = capture(mdesc, {
|
|
1485
|
+
responses:
|
|
1486
|
+
// '`$ANY`': { content: { 'application/json': { schema: '`$CAPTURE`' } } },
|
|
1487
|
+
['`$SELECT`', { '$KEY': { '`$OR`': ['200', '201'] } },
|
|
1488
|
+
{ schema: '`$CAPTURE`' }],
|
|
1489
|
+
|
|
1490
|
+
})
|
|
1491
|
+
schema = caught.schema
|
|
387
1492
|
}
|
|
388
1493
|
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
1494
|
+
if (null == schema) {
|
|
1495
|
+
why.push('no-schema')
|
|
1496
|
+
}
|
|
1497
|
+
else {
|
|
1498
|
+
if (schema.type === 'array') {
|
|
1499
|
+
why.push('array')
|
|
1500
|
+
islist = true
|
|
1501
|
+
}
|
|
393
1502
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
islist = true
|
|
397
|
-
}
|
|
1503
|
+
if (!islist) {
|
|
1504
|
+
const properties = resolveSchemaProperties(schema)
|
|
398
1505
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
) {
|
|
403
|
-
why.push('two-prop:' + prop.key$)
|
|
1506
|
+
each(properties, (prop) => {
|
|
1507
|
+
if (prop.type === 'array') {
|
|
1508
|
+
why.push('array-prop:' + prop.key$)
|
|
404
1509
|
islist = true
|
|
405
1510
|
}
|
|
1511
|
+
})
|
|
1512
|
+
}
|
|
406
1513
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
1514
|
+
if (!islist) {
|
|
1515
|
+
why.push('not-list')
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
411
1519
|
|
|
412
|
-
if (prop.key$ === entdesc.origname) {
|
|
413
|
-
why.push('origname:' + entdesc.origname)
|
|
414
|
-
islist = true
|
|
415
|
-
}
|
|
416
1520
|
|
|
417
|
-
|
|
418
|
-
if (listent === entdesc.name) {
|
|
419
|
-
why.push('listent:' + listent)
|
|
420
|
-
islist = true
|
|
421
|
-
}
|
|
1521
|
+
debugpath(pathStr, mdesc.method, 'IS-LIST', islist, why, schema)
|
|
422
1522
|
|
|
1523
|
+
return islist
|
|
1524
|
+
}
|
|
423
1525
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
1526
|
+
|
|
1527
|
+
function resolveSchemaProperties(schema: any) {
|
|
1528
|
+
let properties: Record<string, any> = {}
|
|
1529
|
+
|
|
1530
|
+
// This is definitely heuristic!
|
|
1531
|
+
if (schema.allOf) {
|
|
1532
|
+
for (let i = schema.allOf.length - 1; -1 < i; --i) {
|
|
1533
|
+
properties = merge([properties, schema.allOf[i].properties || {}])
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
if (schema.properties) {
|
|
1538
|
+
properties = merge([properties, schema.properties])
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
return properties
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
|
|
1550
|
+
|
|
1551
|
+
function updateAction(
|
|
1552
|
+
methodName: string,
|
|
1553
|
+
oldParam: string,
|
|
1554
|
+
actionName: string,
|
|
1555
|
+
entityDesc: EntityDesc,
|
|
1556
|
+
pathdesc: EntityPathDesc,
|
|
1557
|
+
why: string
|
|
1558
|
+
) {
|
|
1559
|
+
if (
|
|
1560
|
+
// Entity not already encoding action.
|
|
1561
|
+
!entityDesc.name.endsWith(canonize(actionName))
|
|
1562
|
+
&& null == pathdesc.action[actionName]
|
|
1563
|
+
) {
|
|
1564
|
+
pathdesc.action[actionName] = {
|
|
1565
|
+
// kind: '`$BOOLEAN`',
|
|
1566
|
+
why_action: ['ent', `${entityDesc.name}`, `${why}`, `${oldParam}`, `${methodName}`]
|
|
429
1567
|
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
430
1571
|
|
|
431
|
-
|
|
432
|
-
|
|
1572
|
+
|
|
1573
|
+
function updateParamRename(
|
|
1574
|
+
ctx: ApiDefContext,
|
|
1575
|
+
data: { def: any, guide: any, work: any },
|
|
1576
|
+
path: string,
|
|
1577
|
+
method: string,
|
|
1578
|
+
paramRenameCapture: {
|
|
1579
|
+
rename: Record<string, string>,
|
|
1580
|
+
why: Record<string, string[]>,
|
|
1581
|
+
},
|
|
1582
|
+
oldParamName: string,
|
|
1583
|
+
newParamName: string,
|
|
1584
|
+
why: string,
|
|
1585
|
+
) {
|
|
1586
|
+
const existingNewName = paramRenameCapture.rename[oldParamName]
|
|
1587
|
+
const existingWhy = paramRenameCapture.why[oldParamName]
|
|
1588
|
+
|
|
1589
|
+
debugpath(path, method, 'UPDATE-PARAM-RENAME', path, oldParamName, newParamName, existingNewName)
|
|
1590
|
+
|
|
1591
|
+
if (null == existingNewName) {
|
|
1592
|
+
paramRenameCapture.rename[oldParamName] = newParamName
|
|
1593
|
+
if (!existingWhy.includes(why)) {
|
|
1594
|
+
existingWhy.push(why)
|
|
433
1595
|
}
|
|
1596
|
+
}
|
|
1597
|
+
else if (newParamName == existingNewName) {
|
|
1598
|
+
// if (!existingWhy.includes(why)) {
|
|
1599
|
+
// existingWhy.push(why)
|
|
434
1600
|
// }
|
|
435
1601
|
}
|
|
1602
|
+
else {
|
|
1603
|
+
ctx.warn({
|
|
1604
|
+
paramRenameCapture, oldParamName, newParamName, why,
|
|
1605
|
+
note: 'Param rename mismatch: existing: ' +
|
|
1606
|
+
oldParamName + ' -> ' + existingNewName + ' (why: ' + existingNewName + ') ' +
|
|
1607
|
+
' proposed: ' + newParamName + ' (why: ' + why + ') ' +
|
|
1608
|
+
'for path: ' + path + '. method: ' + method
|
|
1609
|
+
})
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
436
1612
|
|
|
437
|
-
|
|
1613
|
+
|
|
1614
|
+
|
|
1615
|
+
function isParam(partStr: string) {
|
|
1616
|
+
return '{' === partStr[0] && '}' === partStr[partStr.length - 1]
|
|
438
1617
|
}
|
|
439
1618
|
|
|
440
1619
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
1620
|
+
/*
|
|
1621
|
+
function fixEntName(origName: string) {
|
|
1622
|
+
if (null == origName) {
|
|
1623
|
+
return origName
|
|
1624
|
+
}
|
|
1625
|
+
return depluralize(snakify(origName))
|
|
1626
|
+
}
|
|
1627
|
+
*/
|
|
1628
|
+
|
|
1629
|
+
|
|
1630
|
+
function findcmps(
|
|
1631
|
+
data: { def: any, work: any, guide: any },
|
|
1632
|
+
pathStr: string,
|
|
1633
|
+
underprops: string[],
|
|
1634
|
+
opts?: { uniq?: boolean }
|
|
1635
|
+
): { cmp: string, origcmp: string }[] {
|
|
1636
|
+
const cmplist: string[] = []
|
|
1637
|
+
const cmpset = new Set<string>()
|
|
1638
|
+
|
|
1639
|
+
// TODO: cache in ctx.work
|
|
1640
|
+
|
|
1641
|
+
each(data.def.paths[pathStr])
|
|
1642
|
+
.map((md: MethodDef) => {
|
|
1643
|
+
underprops.map((up: string) => {
|
|
1644
|
+
let found = find((md as any)[up], 'x-ref')
|
|
1645
|
+
|
|
1646
|
+
|
|
1647
|
+
found.map((xref: { val: string }) => {
|
|
1648
|
+
// console.log('FINDCMPS', pathStr, (md as any).key$, up, xref.val)
|
|
1649
|
+
let m = xref.val.match(/\/(components\/schemas|definitions)\/(.+)$/)
|
|
1650
|
+
if (m) {
|
|
1651
|
+
cmplist.push(m[2])
|
|
1652
|
+
cmpset.add(m[2])
|
|
1653
|
+
}
|
|
1654
|
+
})
|
|
1655
|
+
})
|
|
1656
|
+
})
|
|
1657
|
+
// console.log('FOUNDCMPS', cmps)
|
|
1658
|
+
return (opts?.uniq ? Array.from(cmpset) : cmplist).map(n =>
|
|
1659
|
+
({ cmp: canonize(n), origcmp: n }))
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
|
|
1663
|
+
function makeMethodEntityDesc(desc: Record<string, any>): MethodEntityDesc {
|
|
1664
|
+
let ment: MethodEntityDesc = {
|
|
1665
|
+
cmp: desc.cmp ?? null,
|
|
1666
|
+
origcmp: desc.origcmp ?? null,
|
|
1667
|
+
origcmpref: desc.origcmpref ?? null,
|
|
1668
|
+
|
|
1669
|
+
ref: desc.ref ?? '',
|
|
1670
|
+
why_cmp: desc.why_cmp ?? [],
|
|
1671
|
+
cmpoccur: desc.cmpoccur ?? 0,
|
|
1672
|
+
path_rate: desc.path_rate ?? 0,
|
|
1673
|
+
method_rate: desc.method_rate ?? 0,
|
|
1674
|
+
entname: desc.entname ?? '',
|
|
1675
|
+
why_op: desc.why_op ?? [],
|
|
1676
|
+
rename: desc.rename ?? { param: {} },
|
|
1677
|
+
why_rename: desc.why_rename ?? { why_param: {} },
|
|
1678
|
+
rename_orig: desc.rename_orig ?? [],
|
|
1679
|
+
opname: desc.opname ?? '',
|
|
1680
|
+
why_opname: desc.why_opname ?? [],
|
|
1681
|
+
}
|
|
1682
|
+
return ment
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
|
|
1686
|
+
function findPotentialSchemaRefs(pathStr: string, methodName: string, responses: any) {
|
|
1687
|
+
const xrefs: string[] = []
|
|
1688
|
+
const rescodes = ['200', '201']
|
|
1689
|
+
for (let rescode of rescodes) {
|
|
1690
|
+
const schema = getResponseSchema(responses[rescode])
|
|
1691
|
+
if (null != schema) {
|
|
1692
|
+
if (null != schema['x-ref']) {
|
|
1693
|
+
xrefs.push(schema['x-ref'])
|
|
1694
|
+
}
|
|
1695
|
+
else if ('array' === schema.type && null != schema.items?.['x-ref']) {
|
|
1696
|
+
xrefs.push(schema.items?.['x-ref'])
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
446
1699
|
}
|
|
1700
|
+
|
|
1701
|
+
debugpath(pathStr, methodName, 'POTENTIAL-SCHEMA-REFS', xrefs)
|
|
1702
|
+
return xrefs
|
|
447
1703
|
}
|
|
448
1704
|
|
|
449
1705
|
|
|
1706
|
+
function hasMethod(def: any, pathStr: string, methodName: string) {
|
|
1707
|
+
const pathDef = def?.paths?.[pathStr]
|
|
1708
|
+
const found = (
|
|
1709
|
+
null != pathDef
|
|
1710
|
+
&& (
|
|
1711
|
+
null != pathDef[methodName.toLowerCase()]
|
|
1712
|
+
|| null != pathDef[methodName.toUpperCase()]
|
|
1713
|
+
)
|
|
1714
|
+
)
|
|
1715
|
+
console.log('hasMethod', pathStr, methodName, found)
|
|
1716
|
+
return found
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
/*
|
|
1722
|
+
// Some decisions require the full list of potential entities.
|
|
1723
|
+
function reviewEntityDescs(ctx: ApiDefContext, result: any) {
|
|
1724
|
+
const guide: Guide = result.data.guide
|
|
1725
|
+
const metrics = guide.metrics
|
|
1726
|
+
const entityDescs = result.data.work.entmap
|
|
1727
|
+
|
|
1728
|
+
if (0 < metrics.count.cmp) {
|
|
1729
|
+
items(entityDescs).map(([entname, entdesc]: [string, EntityDesc]) => {
|
|
1730
|
+
|
|
1731
|
+
// Entities with a single path and single op and no cmp are suspicious
|
|
1732
|
+
|
|
1733
|
+
const pathmap = entdesc.path
|
|
1734
|
+
const pathcount = size(pathmap)
|
|
1735
|
+
const hascmp = null != entdesc.cmp?.namedesc
|
|
1736
|
+
|
|
1737
|
+
if (
|
|
1738
|
+
1 === pathcount
|
|
1739
|
+
&& !hascmp
|
|
1740
|
+
) {
|
|
1741
|
+
let pathdesc: EntityPathDesc = each(pathmap)[0]
|
|
1742
|
+
const pathStr = (pathdesc as any).key$
|
|
1743
|
+
|
|
1744
|
+
if (1 === size(pathdesc.op)) {
|
|
1745
|
+
let op = pathdesc.op
|
|
1746
|
+
|
|
1747
|
+
// console.log('REVIEW', entdesc.name, pathcount, hascmp, op)
|
|
1748
|
+
|
|
1749
|
+
if (op.create) {
|
|
1750
|
+
|
|
1751
|
+
// Entities without "good" components
|
|
1752
|
+
if (
|
|
1753
|
+
entname.includes('_')
|
|
1754
|
+
&& pathdesc.pm.expr.endsWith('p/t/')
|
|
1755
|
+
) {
|
|
1756
|
+
const lastpart = canonize(getelem(pathdesc.pm, -1))
|
|
1757
|
+
const tgtent = entityDescs[lastpart]
|
|
1758
|
+
|
|
1759
|
+
// console.log('REVIEW', entname, entdesc.cmp, size(pathmap), lastpart, realent)
|
|
1760
|
+
|
|
1761
|
+
if (
|
|
1762
|
+
null != tgtent
|
|
1763
|
+
&& tgtent.name !== entname
|
|
1764
|
+
&& (
|
|
1765
|
+
null == tgtent.cmp
|
|
1766
|
+
|| lastpart == tgtent.cmp
|
|
1767
|
+
)
|
|
1768
|
+
) {
|
|
1769
|
+
|
|
1770
|
+
// Actually a known component
|
|
1771
|
+
// console.dir(entdesc, { depth: null })
|
|
1772
|
+
|
|
1773
|
+
|
|
1774
|
+
const realent = guide.entity[entname]
|
|
1775
|
+
const realpathmap = realent.path
|
|
1776
|
+
let realpath = realpathmap[pathStr]
|
|
1777
|
+
|
|
1778
|
+
if (null == realpath) {
|
|
1779
|
+
realpath = realpathmap[pathStr] = pathdesc
|
|
1780
|
+
}
|
|
1781
|
+
else if (null == realpath.op?.create) {
|
|
1782
|
+
realpath.op = (realpath.op ?? {})
|
|
1783
|
+
realpath.op.create = pathdesc.op.create
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
realpath.op.create.why_op =
|
|
1787
|
+
'was/create/A:' + entname + ':' + realpath.op.create.why_op
|
|
1788
|
+
|
|
1789
|
+
delete entityDescs[entname]
|
|
1790
|
+
|
|
1791
|
+
// console.log('REPLACE', entname, realent.name, realpath)
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
else if (op.remove) {
|
|
1798
|
+
const otherents: EntityDesc[] = each(entityDescs)
|
|
1799
|
+
.filter((ed: EntityDesc) => ed !== entdesc && each(ed.path)
|
|
1800
|
+
.filter(epd => epd.key$ === pathStr).length)
|
|
1801
|
+
|
|
1802
|
+
const otherent = 1 === otherents.length ? otherents[0] : null
|
|
1803
|
+
|
|
1804
|
+
// console.log('OTHERENT', pathStr, otherents.length, otherent)
|
|
1805
|
+
|
|
1806
|
+
if (null != otherent && null != otherent.cmp) {
|
|
1807
|
+
const otherpath = otherent.path[pathStr]
|
|
1808
|
+
|
|
1809
|
+
if (null == otherpath.op.remove) {
|
|
1810
|
+
otherpath.op.remove = op.remove
|
|
1811
|
+
otherpath.op.remove.why_op =
|
|
1812
|
+
'was/delete/A:' + entname + ':' + op.remove.why_op
|
|
1813
|
+
delete entityDescs[entname]
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
debugpath(pathdesc.pm.path, null,
|
|
1819
|
+
'REVIEW-ENTITY', formatJSONIC(entdesc, { hsepd: 0, $: true, color: true }))
|
|
1820
|
+
|
|
1821
|
+
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
})
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
*/
|
|
1828
|
+
|
|
450
1829
|
|
|
451
1830
|
export {
|
|
452
1831
|
heuristic01
|