@voxgig/apidef 2.2.0 → 2.3.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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { each, snakify, names } from 'jostraca'
4
4
 
5
- import { size } from '@voxgig/struct'
5
+ import { size, walk } from '@voxgig/struct'
6
6
 
7
7
 
8
8
  import {
@@ -14,6 +14,7 @@ import {
14
14
  type EntityDesc = {
15
15
  name: string
16
16
  origname: string
17
+ why_name?: string[]
17
18
  plural: string
18
19
  path: Record<string, EntityPathDesc>
19
20
  alias: Record<string, string>
@@ -22,6 +23,7 @@ type EntityDesc = {
22
23
 
23
24
  type EntityPathDesc = {
24
25
  op: Record<string, any>
26
+ why_ent: string[]
25
27
  }
26
28
 
27
29
  // Log non-fatal wierdness.
@@ -32,17 +34,11 @@ async function heuristic01(ctx: any): Promise<Record<string, any>> {
32
34
 
33
35
  const entityDescs = resolveEntityDescs(ctx)
34
36
 
35
- // console.log('entityDescs')
36
- // console.dir(entityDescs, { depth: null })
37
-
38
37
  guide = {
39
38
  control: guide.control,
40
39
  entity: entityDescs,
41
40
  }
42
41
 
43
- // console.log('GUIDE')
44
- // console.dir(guide, { depth: null })
45
-
46
42
  return guide
47
43
  }
48
44
 
@@ -73,14 +69,19 @@ function resolveEntityDescs(ctx: any) {
73
69
  // const entdesc = resolveEntity(entityDescs, pathStr, m[1], m[2])
74
70
 
75
71
  each(pathDef, (methodDef: any, methodStr: string) => {
76
- methodStr = methodStr.toLowerCase()
72
+ // console.log('PPP', pathStr, methodStr, methodDef)
77
73
 
74
+ methodStr = methodStr.toLowerCase()
75
+ let why_op: string[] = []
78
76
 
79
77
  if (!METHOD_IDOP[methodStr]) {
80
78
  return
81
79
  }
82
80
 
83
- const entdesc = resolveEntity(entityDescs, pathDef, pathStr, methodDef, methodStr)
81
+ const why_ent: string[] = []
82
+ const entdesc =
83
+ resolveEntity(entityDescs, pathDef, pathStr, methodDef, methodStr, why_ent)
84
+
84
85
 
85
86
  if (null == entdesc) {
86
87
  console.log(
@@ -89,12 +90,15 @@ function resolveEntityDescs(ctx: any) {
89
90
  return
90
91
  }
91
92
 
93
+ entdesc.path[pathStr].why_ent = why_ent
94
+
95
+
92
96
  // if (pathStr.includes('courses')) {
93
97
  // console.log('ENTRES', pathStr, methodStr)
94
98
  // console.dir(ent2, { depth: null })
95
99
  // }
96
100
 
97
- let opname = resolveOpName(methodStr, methodDef, pathStr, entdesc)
101
+ let opname = resolveOpName(methodStr, methodDef, pathStr, entdesc, why_op)
98
102
 
99
103
  if (null == opname) {
100
104
  console.log(
@@ -109,7 +113,7 @@ function resolveEntityDescs(ctx: any) {
109
113
  // resform: '`body`',
110
114
  }
111
115
 
112
- const resokdef = methodDef.responses[200] || methodDef.responses[201]
116
+ const resokdef = methodDef.responses?.[200] || methodDef.responses?.[201]
113
117
  const resbody = resokdef?.content?.['application/json']?.schema
114
118
  if (resbody) {
115
119
  if (resbody[entdesc.origname]) {
@@ -136,16 +140,17 @@ function resolveEntityDescs(ctx: any) {
136
140
  op[opname] = {
137
141
  // TODO: in actual guide, remove "standard" method ops since redundant
138
142
  method: methodStr,
143
+ why_op: why_op.join(';')
139
144
  }
140
145
 
141
146
  if (0 < Object.entries(transform).length) {
142
147
  op[opname].transform = transform
143
148
  }
144
149
 
145
- if ('/v2/users/{user_id}/enrollment' === pathStr) {
146
- console.log('ENT')
147
- console.dir(entdesc, { depth: null })
148
- }
150
+ // if ('/v2/users/{user_id}/enrollment' === pathStr) {
151
+ // console.log('ENT')
152
+ // console.dir(entdesc, { depth: null })
153
+ // }
149
154
  })
150
155
  }
151
156
  })
@@ -163,24 +168,32 @@ function resolveEntity(
163
168
  pathStr: string,
164
169
  methodDef: Record<string, any>,
165
170
  methodStr: string,
171
+ why_ent: string[]
166
172
  ): EntityDesc | undefined {
167
173
 
168
174
  let entdesc: EntityDesc
169
175
  let entname: string = ''
170
176
  let origentname: string = ''
171
177
 
178
+ const why_name: string[] = []
179
+
172
180
  const m = pathStr.match(/\/([a-zA-Z0-1_-]+)(\/\{([a-zA-Z0-1_-]+)\})?$/)
173
181
  if (m) {
174
182
  let pathName = m[1]
175
183
  origentname = snakify(pathName)
184
+ entname = depluralize(origentname)
176
185
 
177
186
  // Check schema
178
- const compname = resolveComponentName(methodDef, methodStr)
187
+ const compname = resolveComponentName(entname, methodDef, methodStr, pathStr, why_name)
179
188
  if (compname) {
180
189
  origentname = snakify(compname)
190
+ entname = depluralize(origentname)
191
+ why_ent.push('cmp:' + entname)
192
+ }
193
+ else {
194
+ why_ent.push('path:' + m[1])
195
+ why_name.push('path:' + m[1])
181
196
  }
182
-
183
- entname = depluralize(origentname)
184
197
 
185
198
  entdesc = (entityDescs[entname] = entityDescs[entname] || {
186
199
  name: entname,
@@ -216,6 +229,10 @@ function resolveEntity(
216
229
  entdesc.path[pathStr] = entdesc.path[pathStr] || {}
217
230
  entdesc.path[pathStr].op = entdesc.path[pathStr].op || {}
218
231
 
232
+ if (null == entdesc.why_name) {
233
+ entdesc.why_name = why_name
234
+ }
235
+
219
236
  return entdesc
220
237
  }
221
238
 
@@ -229,12 +246,44 @@ const REQKIND: any = {
229
246
 
230
247
 
231
248
  function resolveComponentName(
249
+ entname: string,
232
250
  methodDef: Record<string, any>,
233
251
  methodStr: string,
252
+ pathStr: string,
253
+ why_name: string[]
234
254
  ): string | undefined {
235
- const kind = REQKIND[methodStr]
236
255
  let compname: string | undefined = undefined
237
256
 
257
+ let xrefs = find(methodDef, 'x-ref')
258
+ .filter(xref => xref.val.includes('schema'))
259
+
260
+ // TODO: identify non-ent schemas
261
+ .filter(xref => !xref.val.includes('Meta'))
262
+
263
+ .sort((a, b) => a.path.length - b.path.length)
264
+
265
+ // console.log('RCN', pathStr, methodStr, xrefs.map(x => [x.val, x.path.length]))
266
+
267
+ let first = xrefs[0]?.val
268
+
269
+ if (null != first) {
270
+ let xrefm = (first as string).match(/\/components\/schemas\/(.+)$/)
271
+ if (xrefm) {
272
+ why_name.push('cmp')
273
+ compname = xrefm[1]
274
+ }
275
+ }
276
+
277
+ if (null != compname) {
278
+ compname = depluralize(snakify(compname))
279
+
280
+ // Assume sub schemas suffixes are not real entities
281
+ if (compname.includes(entname)) {
282
+ compname = compname.slice(0, compname.indexOf(entname) + entname.length)
283
+ }
284
+ }
285
+
286
+ /*
238
287
  const responses = methodDef.responses
239
288
  const schemalist =
240
289
  [
@@ -245,9 +294,18 @@ function resolveComponentName(
245
294
  .filter(cmp => null != cmp)
246
295
  .map(content => content['application/json']?.schema)
247
296
  .filter(schema => null != schema)
248
- .filter(schema => null != schema['x-ref'])
297
+ // .filter(schema => null != schema['x-ref'])
249
298
  .map(schema => {
250
- let xrefm = schema['x-ref'].match(/\/components\/schemas\/(.+)$/)
299
+
300
+ let xrefs = find(schema, 'x-ref')
301
+
302
+ if ('responses' === pathName) {
303
+ console.log('xrefs', xrefs)
304
+ }
305
+
306
+ let xrefv = String(xrefs[0])
307
+
308
+ let xrefm = xrefv.match(/\/components\/schemas\/(.+)$/)
251
309
  if (xrefm) {
252
310
  schema['x-ref-cmp'] = xrefm[1]
253
311
  }
@@ -255,9 +313,18 @@ function resolveComponentName(
255
313
  })
256
314
  .filter(schema => null != schema['x-ref-cmp'])
257
315
 
316
+ if ('responses' === pathName) {
317
+ console.log('CMP', pathName, schemalist.length)
318
+ // console.dir(methodDef.responses['200'].content['application/json'].schema, { depth: null })
319
+ }
320
+
258
321
  let schema = undefined
259
322
  let splen = -1
260
323
 
324
+ if (0 < schemalist.length) {
325
+ why_name.push('schema')
326
+ }
327
+
261
328
  for (let sI = 0; sI < schemalist.length; sI++) {
262
329
  let nextschema = schemalist[sI]
263
330
  let nsplen = nextschema.properties?.length || -1
@@ -282,6 +349,7 @@ function resolveComponentName(
282
349
  // console.log('RCN-XREF', methodStr, 'xref-0', xref)
283
350
 
284
351
  if (null == xref) {
352
+ why_name.push('xref')
285
353
  const properties = schema.properties || {}
286
354
  each(properties, (prop) => {
287
355
  if (null == xref) {
@@ -296,26 +364,42 @@ function resolveComponentName(
296
364
  if (null != xref && 'string' === typeof xref) {
297
365
  let xrefm = xref.match(/\/components\/schemas\/(.+)$/)
298
366
  if (xrefm) {
367
+ why_name.push('cmp')
299
368
  compname = xrefm[1]
300
369
  }
301
370
  }
302
371
  }
372
+ */
303
373
 
304
374
  return compname
305
375
  }
306
376
 
307
377
 
308
- function resolveOpName(methodStr: string, methodDef: any, pathStr: string, entdesc: EntityDesc)
378
+ function resolveOpName(
379
+ methodStr: string,
380
+ methodDef: any,
381
+ pathStr: string,
382
+ entdesc: EntityDesc,
383
+ why: string[]
384
+ )
309
385
  : string | undefined {
386
+ // console.log('ROP', pathStr, methodDef)
387
+
310
388
 
311
389
  let opname = METHOD_IDOP[methodStr]
312
- if (null == opname) return;
390
+ if (null == opname) {
391
+ why.push('no-op:' + methodStr)
392
+ return
393
+ }
313
394
 
314
395
  if ('load' === opname) {
315
- const islist = isListResponse(methodDef, pathStr, entdesc)
396
+ const islist = isListResponse(methodDef, pathStr, entdesc, why)
316
397
  opname = islist ? 'list' : opname
317
398
 
318
- console.log('ISLIST', entdesc.name, methodStr, opname, pathStr)
399
+ // console.log('ISLIST', entdesc.name, methodStr, opname, pathStr)
400
+ }
401
+ else {
402
+ why.push('not-load')
319
403
  }
320
404
 
321
405
  return opname
@@ -325,7 +409,8 @@ function resolveOpName(methodStr: string, methodDef: any, pathStr: string, entde
325
409
  function isListResponse(
326
410
  methodDef: Record<string, any>,
327
411
  pathStr: string,
328
- entdesc: EntityDesc
412
+ entdesc: EntityDesc,
413
+ why: string[]
329
414
  ): boolean {
330
415
  const responses = methodDef.responses
331
416
  const resdef = responses?.['201'] || responses?.['200']
@@ -333,10 +418,19 @@ function isListResponse(
333
418
 
334
419
  let islist = false
335
420
 
336
- if (null != content) {
421
+ if (null == content) {
422
+ // console.log('NO-CONTENT', pathStr, methodDef)
423
+ why.push('no-content')
424
+ }
425
+ else {
337
426
  const schema = content['application/json']?.schema
338
- if (schema) {
427
+ if (null == schema) {
428
+ why.push('no-schema')
429
+ }
430
+ else {
431
+
339
432
  if (schema.type === 'array') {
433
+ why.push('array')
340
434
  islist = true
341
435
  }
342
436
 
@@ -345,21 +439,46 @@ function isListResponse(
345
439
  each(properties, (prop) => {
346
440
  if (prop.type === 'array') {
347
441
 
348
- if (
349
- 1 === size(properties) ||
350
- prop.key$ === entdesc.name ||
351
- prop.key$ === entdesc.origname ||
352
- listedEntity(prop) === entdesc.name
442
+ if (1 === size(properties)) {
443
+ why.push('one-prop:' + prop.key$)
444
+ islist = true
445
+ }
446
+
447
+ if (2 === size(properties) &&
448
+ ('data' === prop.key$ ||
449
+ 'list' === prop.key$)
353
450
  ) {
451
+ why.push('two-prop:' + prop.key$)
354
452
  islist = true
355
453
  }
356
454
 
357
- if ('/v2/users' === pathStr) {
358
- console.log('islistresponse', islist, pathStr, entdesc.name, listedEntity(prop), properties)
455
+ if (prop.key$ === entdesc.name) {
456
+ why.push('name:' + entdesc.origname)
457
+ islist = true
458
+ }
459
+
460
+ if (prop.key$ === entdesc.origname) {
461
+ why.push('origname:' + entdesc.origname)
462
+ islist = true
359
463
  }
464
+
465
+ const listent = listedEntity(prop)
466
+ if (listent === entdesc.name) {
467
+ why.push('listent:' + listent)
468
+ islist = true
469
+ }
470
+
471
+
472
+ // if ('/v2/users' === pathStr) {
473
+ // console.log('islistresponse', islist, pathStr, entdesc.name, listedEntity(prop), properties)
474
+ // }
360
475
  }
361
476
  })
362
477
  }
478
+
479
+ if (!islist) {
480
+ why.push('not-list')
481
+ }
363
482
  }
364
483
  }
365
484
 
@@ -376,6 +495,17 @@ function listedEntity(prop: any) {
376
495
  }
377
496
 
378
497
 
498
+ function find(obj: any, qkey: string): any[] {
499
+ let vals: any[] = []
500
+ walk(obj, (key: any, val: any, _p: any, t: string[]) => {
501
+ if (qkey === key) {
502
+ vals.push({ key, val, path: t })
503
+ }
504
+ return val
505
+ })
506
+ return vals
507
+ }
508
+
379
509
 
380
510
  export {
381
511
  heuristic01
package/src/guide.ts CHANGED
@@ -61,13 +61,16 @@ async function resolveGuide(ctx: any) {
61
61
 
62
62
  guideBlocks.push(...each(baseguide.entity, (entity, entityname) => {
63
63
  guideBlocks.push(`
64
- entity: ${entityname}: {`)
64
+ entity: ${entityname}: {` +
65
+ (0 < entity.why_name.length ? ' # name:' + entity.why_name.join(';') : ''))
65
66
 
66
67
  each(entity.path, (path, pathname) => {
67
- guideBlocks.push(` path: '${pathname}': op: {`)
68
+ guideBlocks.push(` path: '${pathname}': op: {` +
69
+ (0 < path.why_ent.length ? ' # ent:' + path.why_ent.join(';') : ''))
68
70
 
69
71
  each(path.op, (op, opname) => {
70
- guideBlocks.push(` '${opname}': method: ${op.method}`)
72
+ guideBlocks.push(` '${opname}': method: ${op.method}` +
73
+ (0 < op.why_op.length ? ' # ' + op.why_op : ''))
71
74
  if (op.transform?.reqform) {
72
75
  guideBlocks.push(
73
76
  ` '${opname}': transform: reqform: ${JSON.stringify(op.transform.reqform)}`)
@@ -110,7 +113,11 @@ function cleanGuide(guide: Record<string, any>): Record<string, any> {
110
113
  }
111
114
  }
112
115
 
113
- let ent: any = clean.entity[name] = clean.entity[name] = { name, path: {} }
116
+ let ent: any = clean.entity[name] = clean.entity[name] = {
117
+ name,
118
+ why_name: entity.why_name || [],
119
+ path: {}
120
+ }
114
121
 
115
122
  each(entity.path, (path: any, pathname: string) => {
116
123
  ent.path[pathname] = path
@@ -13,10 +13,6 @@ import { depluralize } from '../utility'
13
13
 
14
14
  const operationTransform = async function(
15
15
  ctx: any,
16
- // guide: Guide,
17
- // // tspec: TransformSpec,
18
- // model: any,
19
- // def: any
20
16
  ): Promise<TransformResult> {
21
17
  const { apimodel, model, def } = ctx
22
18
  const guide = model.main.api.guide
@@ -265,24 +261,38 @@ const operationTransform = async function(
265
261
 
266
262
 
267
263
  const opBuilder: any = {
268
- any: (entityModel: any, pathdef: any, op: any, path: any, entity: any, model: any) => {
269
- const opname = op.key$
270
- const method = op.method
264
+ any: (
265
+ entityModel: any,
266
+ pathdef: any,
267
+ guideOp: any,
268
+ guidePath: any,
269
+ guideEntity: any,
270
+ model: any
271
+ ) => {
272
+ if (false === guidePath.active) {
273
+ return
274
+ }
275
+
276
+ const opname = guideOp.key$
277
+ const method = guideOp.method
271
278
  const kind = OPKIND[opname]
272
279
 
280
+ const existingOpModel = entityModel.op[opname]
281
+ const existingParam = existingOpModel?.param
282
+
273
283
  const [resform, resform_COMMENT] =
274
- resolveTransform(entityModel, op, kind, 'resform', pathdef)
284
+ resolveTransform(entityModel, guideOp, kind, 'resform', pathdef)
275
285
 
276
286
  const [reqform, reqform_COMMENT] =
277
- resolveTransform(entityModel, op, kind, 'reqform', pathdef)
287
+ resolveTransform(entityModel, guideOp, kind, 'reqform', pathdef)
278
288
 
279
289
  const opModel = {
280
- path: path.key$,
290
+ path: guidePath.key$,
281
291
  pathalt: ([] as any[]),
282
292
 
283
293
  method,
284
294
  kind,
285
- param: {},
295
+ param: existingParam || {},
286
296
  query: {},
287
297
 
288
298
  resform_COMMENT: 'derivation: ' + resform_COMMENT,
@@ -296,12 +306,12 @@ const operationTransform = async function(
296
306
  }
297
307
  }
298
308
 
299
- fixName(opModel, op.key$)
309
+ fixName(opModel, guideOp.key$)
300
310
 
301
311
  let params: any[] = []
302
312
 
303
313
  // Params are in the path
304
- if (0 < path.params$.length) {
314
+ if (0 < guidePath.params$.length) {
305
315
  let sharedparams = getx(pathdef, 'parameters?in=path') || []
306
316
  params = sharedparams.concat(
307
317
  getx(pathdef[method], 'parameters?in=path') || []
@@ -310,7 +320,7 @@ const operationTransform = async function(
310
320
  // if (Array.isArray(params)) {
311
321
  params.reduce((a: any, p: any) =>
312
322
  (paramBuilder(a, p, opModel, entityModel,
313
- pathdef, op, path, entity, model), a), opModel.param)
323
+ pathdef, guideOp, guidePath, guideEntity, model), a), opModel.param)
314
324
  //}
315
325
  }
316
326
 
@@ -319,10 +329,11 @@ const operationTransform = async function(
319
329
  let queries = sharedqueries.concat(getx(pathdef[method], 'parameters?in!=path') || [])
320
330
  queries.reduce((a: any, p: any) =>
321
331
  (queryBuilder(a, p, opModel, entityModel,
322
- pathdef, op, path, entity, model), a), opModel.query)
332
+ pathdef, guideOp, guidePath, guideEntity, model), a), opModel.query)
323
333
 
324
334
  let pathalt: any[] = []
325
- const pathselector = makePathSelector(path.key$) // , params)
335
+ const pathselector = makePathSelector(guidePath.key$)
336
+
326
337
  let before = false
327
338
 
328
339
  if (null != entityModel.op[opname]) {
@@ -405,18 +416,39 @@ const operationTransform = async function(
405
416
  each(guideEntity.path, (guidePath: any) => {
406
417
  const pathdef = def.paths[guidePath.key$]
407
418
 
408
- each(guidePath.op, (op: any) => {
409
- const opbuild = opBuilder[op.key$]
419
+ each(guidePath.op, (guideOp: any) => {
420
+ const opbuild = opBuilder[guideOp.key$]
410
421
 
411
422
  if (opbuild) {
412
- opbuild(entityModel, pathdef, op, guidePath, guideEntity, model)
423
+ opbuild(entityModel, pathdef, guideOp, guidePath, guideEntity, model)
413
424
  opcount++
414
425
  }
415
426
  })
427
+ })
416
428
 
417
-
429
+ // Full list of params only know after all operations built.
430
+ each(entityModel.op, (op: any) => {
431
+ const params = Object.keys(op.param || {})
432
+ const pathalt = op.pathalt || []
433
+
434
+ // if ('course' === entityModel.name) {
435
+ // console.log('PA', params, pathalt)
436
+ // }
437
+
438
+ for (const pa of pathalt) {
439
+ for (const p of params) {
440
+ pa[p] = pa[p] || false
441
+ // if ('course' === entityModel.name) {
442
+ // console.log('PA-SET', p, pa)
443
+ // }
444
+ }
445
+ }
418
446
  })
419
447
 
448
+ // if ('course' === entityModel.name) {
449
+ // console.dir(entityModel, { depth: null })
450
+ // }
451
+
420
452
  msg += guideEntity.name + '=' + opcount + ' '
421
453
  })
422
454
 
@@ -424,7 +456,7 @@ const operationTransform = async function(
424
456
  }
425
457
 
426
458
 
427
- function makePathSelector(path: string) { // , params: any[]) {
459
+ function makePathSelector(path: string) {
428
460
  let out: any = { path }
429
461
 
430
462
  let pn$ = 0
@@ -435,11 +467,6 @@ function makePathSelector(path: string) { // , params: any[]) {
435
467
  }
436
468
  out.pn$ = pn$
437
469
 
438
- // for (let p of params) {
439
- // setprop(out, p.name, getprop(out, p.name, false))
440
- // console.log('SP', p.name, out[p.name])
441
- // }
442
-
443
470
  return out
444
471
  }
445
472