@descryy/adapter-python 0.4.1 → 0.5.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/adapter.d.ts.map +1 -1
- package/dist/adapter.js +32 -1
- package/dist/adapter.js.map +1 -1
- package/dist/client-base.d.ts +63 -0
- package/dist/client-base.d.ts.map +1 -0
- package/dist/client-base.js +55 -0
- package/dist/client-base.js.map +1 -0
- package/dist/client.d.ts +29 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +70 -29
- package/dist/client.js.map +1 -1
- package/dist/extract.d.ts.map +1 -1
- package/dist/extract.js +220 -48
- package/dist/extract.js.map +1 -1
- package/dist/response-shape.d.ts +62 -0
- package/dist/response-shape.d.ts.map +1 -0
- package/dist/response-shape.js +95 -0
- package/dist/response-shape.js.map +1 -0
- package/package.json +4 -4
package/dist/extract.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* because `a/__init__.py` importing from `b/__init__.py` importing back is legal
|
|
31
31
|
* Python and must not hang the run.
|
|
32
32
|
*/
|
|
33
|
-
import { databaseTableNodesFromModel, edgeId, endpointQsp, isCallableNodeType, isTypeLikeNodeType, nodeId, normaliseEndpointPath, symbolQsp, testCaseQsp, } from "@descryy/ir";
|
|
33
|
+
import { databaseTableNodesFromModel, edgeId, endpointQsp, externalQsp, isCallableNodeType, isTypeLikeNodeType, nodeId, normaliseEndpointPath, symbolQsp, testCaseQsp, } from "@descryy/ir";
|
|
34
34
|
import { importRoots, modulePathOf, moduleNameOf, resolveImport, } from "./module.js";
|
|
35
35
|
import { attributeExternal, externalImportTable, ownNamespaces, STDLIB_BASIS, } from "./external.js";
|
|
36
36
|
import { DECLARATIVE_BASE_FACTORIES, ORM_BASE_MODULES, isFrameworkBaseName, ormShapeOf, } from "./orm.js";
|
|
@@ -39,7 +39,16 @@ import { drfShapeOf, dtoShapeOf, isDrfSerializerRoot, isPydanticRoot, isValidato
|
|
|
39
39
|
import { DRF_ACTIONS, drfRegisterCallsIn, drfRouterLocalsIn, djangoUrlEntriesIn, HTTP_METHODS, isApplicationRouter, joinPath, mountsIn, odooRouteFromDecorator, routeFromDecorator, routersIn, } from "./routes.js";
|
|
40
40
|
import { GRAPHQL_METHOD, fieldFromMethod, fieldsFromAttributes, operationPath, schemaDeclsIn, } from "./graphql.js";
|
|
41
41
|
import { clientCallsIn, clientLocalsIn } from "./client.js";
|
|
42
|
+
import { modelParsing, validateCalls } from "./response-shape.js";
|
|
42
43
|
export const LANGUAGE = "python";
|
|
44
|
+
/** Which packaging world an external module name belongs to. The name is the
|
|
45
|
+
* ecosystem's, not an installed path's: `pyyaml` installs `yaml`, and `yaml`
|
|
46
|
+
* is what the source writes and what this node is identified by. */
|
|
47
|
+
const EXTERNAL_ECOSYSTEM = "pypi";
|
|
48
|
+
/** An external node is attributed from the file's own `import`/`from`
|
|
49
|
+
* statements, which is module resolution and nothing more. R1 exactly: never
|
|
50
|
+
* R0 (the import *is* the resolution), never R4 (nothing observed it run). */
|
|
51
|
+
const EXTERNAL_RESOLUTION = 1;
|
|
43
52
|
/**
|
|
44
53
|
* Confidence by the level an edge's evidence earned — never by the level the
|
|
45
54
|
* run reached. DEC-058: those were two different notions in the first adapter,
|
|
@@ -79,10 +88,6 @@ const REASONS = {
|
|
|
79
88
|
callBaseUnresolved: "an HTTP call written as f\"{BASE}/path\" whose base this file does not bind to a string " +
|
|
80
89
|
"literal. The path half is readable and the base half is not, so the served template cannot " +
|
|
81
90
|
"be assembled — the named base is the piece of work that would close it.",
|
|
82
|
-
callBaseIsExternalHost: "an HTTP call written as f\"{BASE}/path\" whose base resolves to an absolute URL on another " +
|
|
83
|
-
"host. Read correctly and out of scope for the join: an endpoint is identified by method and " +
|
|
84
|
-
"path with no host (DEC-014), so claiming one here would let this caller join a local route " +
|
|
85
|
-
"of the same path that it never reaches.",
|
|
86
91
|
callPathFromLocal: "an HTTP call whose path is a local assembled earlier in the function. Distinguished from an " +
|
|
87
92
|
"opaque path because it is reachable by further work, and filing the two together would " +
|
|
88
93
|
"report one number for two populations.",
|
|
@@ -354,6 +359,24 @@ export function extract(input) {
|
|
|
354
359
|
const edges = [];
|
|
355
360
|
const unresolved = [];
|
|
356
361
|
const seenEdge = new Set();
|
|
362
|
+
/**
|
|
363
|
+
* DEC-164's `attrs.clientBase`, per caller node id. First writer wins, so a
|
|
364
|
+
* function making two calls is described by the first one read rather than
|
|
365
|
+
* by whichever happened to be walked last.
|
|
366
|
+
*
|
|
367
|
+
* **Declared HERE, beside `nodes`, and the reason is a bug this already
|
|
368
|
+
* caused.** It sat next to the R2 guard, which reads as the right place —
|
|
369
|
+
* the client half is R2 work. But the client half runs inside `emitRoutes`,
|
|
370
|
+
* and `emitRoutes` is *called* above that line, so every run threw
|
|
371
|
+
* `ReferenceError: Cannot access 'clientBases' before initialization`,
|
|
372
|
+
* `extractOrDegrade` turned the throw into a degraded one-node-per-file
|
|
373
|
+
* graph, and the corpus reported all nineteen patterns as role-unbound
|
|
374
|
+
* failures rather than as a crash. A temporal-dead-zone error inside a
|
|
375
|
+
* catch-all degrade path looks exactly like an adapter that read nothing.
|
|
376
|
+
* The map stays empty below R2 because nothing writes to it there, which is
|
|
377
|
+
* the honest version of what the old placement was trying to say.
|
|
378
|
+
*/
|
|
379
|
+
const clientBases = new Map();
|
|
357
380
|
/** file -> declaration name (first segment) -> Declared */
|
|
358
381
|
const declaredIn = new Map();
|
|
359
382
|
/** node id -> the entry owning it, where it sits in `nodes`, and how many
|
|
@@ -430,19 +453,26 @@ export function extract(input) {
|
|
|
430
453
|
}));
|
|
431
454
|
assignedOf.set(file, new Set(parsed.assignedNames ?? []));
|
|
432
455
|
}
|
|
456
|
+
/** Node ids already minted for a dependency symbol — one node per symbol, not
|
|
457
|
+
* per call site. Measured node growth is +3.1% (plane) to +4.5% (dispatch). */
|
|
458
|
+
const externalIds = new Map();
|
|
433
459
|
/**
|
|
434
|
-
* The
|
|
435
|
-
*
|
|
460
|
+
* The id of the `FUNCTION` node standing for the symbol behind a call that
|
|
461
|
+
* left the repository, or `undefined` when no binding the developer wrote
|
|
462
|
+
* settles it — or when this run did not reach the resolution such a node
|
|
463
|
+
* requires.
|
|
436
464
|
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
465
|
+
* Three settled rules govern the node
|
|
466
|
+
* (`DEC-NEXT-external-symbol-nodes-at-the-dependency-boundary`): the qsp
|
|
467
|
+
* carries an `ext:` marker (`externalQsp`), the node may claim R1–R3 but
|
|
468
|
+
* never R0 or R4, and it owns no outgoing edge, because nothing read its
|
|
469
|
+
* body. `thirdParty` rides in `attrs`, never in the hash: a module that moved
|
|
470
|
+
* between the standard library and an installed package would otherwise
|
|
471
|
+
* change identity with no line of source changing.
|
|
444
472
|
*/
|
|
445
|
-
const
|
|
473
|
+
const mintExternal = (file, receiver, name) => {
|
|
474
|
+
if (input.reached < EXTERNAL_RESOLUTION)
|
|
475
|
+
return undefined;
|
|
446
476
|
const attribution = attributeExternal({
|
|
447
477
|
receiver,
|
|
448
478
|
name,
|
|
@@ -451,18 +481,49 @@ export function extract(input) {
|
|
|
451
481
|
});
|
|
452
482
|
if (attribution === null)
|
|
453
483
|
return undefined;
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
484
|
+
// Identity, and it is the module path the source imported — never a
|
|
485
|
+
// site-packages directory, never a distribution name, never a version.
|
|
486
|
+
const qsp = externalQsp(attribution.moduleOrNamespace, attribution.symbolPath);
|
|
487
|
+
const existing = externalIds.get(qsp);
|
|
488
|
+
if (existing !== undefined)
|
|
489
|
+
return existing;
|
|
490
|
+
const id = nodeId(scope, "FUNCTION", qsp, LANGUAGE);
|
|
491
|
+
externalIds.set(qsp, id);
|
|
492
|
+
nodes.push({
|
|
493
|
+
id,
|
|
494
|
+
type: "FUNCTION",
|
|
495
|
+
name: attribution.symbolPath[attribution.symbolPath.length - 1] ?? "",
|
|
496
|
+
// No file and no range: the symbol lives in a dependency, and a node that
|
|
497
|
+
// named a file here would be invalidated by a source file it has nothing
|
|
498
|
+
// to do with.
|
|
499
|
+
file: null,
|
|
500
|
+
range: null,
|
|
501
|
+
language: LANGUAGE,
|
|
502
|
+
producedBy: input.producedBy,
|
|
503
|
+
resolution: EXTERNAL_RESOLUTION,
|
|
504
|
+
attrs: {
|
|
460
505
|
thirdParty: attribution.thirdParty,
|
|
461
506
|
// Honest degradation: `thirdParty` is decided against a static list,
|
|
462
|
-
// not against anything installed, and the
|
|
507
|
+
// not against anything installed, and the node says which list.
|
|
463
508
|
thirdPartyBasis: STDLIB_BASIS,
|
|
464
509
|
},
|
|
465
|
-
|
|
510
|
+
external: {
|
|
511
|
+
ecosystem: EXTERNAL_ECOSYSTEM,
|
|
512
|
+
moduleOrNamespace: attribution.moduleOrNamespace,
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
return id;
|
|
516
|
+
};
|
|
517
|
+
/**
|
|
518
|
+
* The edge into a dependency is **always `CALLS`, never `TESTS`**, even when
|
|
519
|
+
* the container is a `TEST_CASE` and every in-repo edge it owns is a `TESTS`.
|
|
520
|
+
* `TESTS` asserts coverage — that this case exercises that node — and this
|
|
521
|
+
* run read nothing of the dependency's body, so it cannot assert it (rule 4,
|
|
522
|
+
* rule 7). A test that reaches `yaml.safe_load` calls it; it does not test
|
|
523
|
+
* it. Filed as `DEC-NEXT-external-edge-is-calls-not-tests`.
|
|
524
|
+
*/
|
|
525
|
+
const callExternal = (from, to) => {
|
|
526
|
+
push({ from, to, type: "CALLS" }, EXTERNAL_RESOLUTION);
|
|
466
527
|
};
|
|
467
528
|
// --- which classes are test suites, decided from evidence not from names ---
|
|
468
529
|
/** file -> top-level class name -> the bases it was written with. */
|
|
@@ -1477,7 +1538,12 @@ export function extract(input) {
|
|
|
1477
1538
|
// left into is readable. This is the half `adapter-go` has no
|
|
1478
1539
|
// analogue for (Go has no from-import), and on measurement it is the
|
|
1479
1540
|
// larger half: 2,790 of 4,836 attributions on dispatch.
|
|
1480
|
-
|
|
1541
|
+
const external = mintExternal(file, undefined, reference.name);
|
|
1542
|
+
if (external !== undefined) {
|
|
1543
|
+
callExternal(source.id, external);
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
disclose(source, edgeType, reference.name, file, reference.line, "outside");
|
|
1481
1547
|
continue;
|
|
1482
1548
|
}
|
|
1483
1549
|
if ((target.type === "FUNCTION" || target.type === "TEST_CASE") && target.id !== source.id) {
|
|
@@ -1560,8 +1626,16 @@ export function extract(input) {
|
|
|
1560
1626
|
// about this row — there is one, and it leaves the repository. So the
|
|
1561
1627
|
// reason becomes `outside`, which is what the row actually is. An
|
|
1562
1628
|
// unattributed row is untouched and stays `unknown`.
|
|
1563
|
-
const external =
|
|
1564
|
-
|
|
1629
|
+
const external = mintExternal(file, reference.receiver, reference.name);
|
|
1630
|
+
if (external !== undefined) {
|
|
1631
|
+
// `import yaml` ... `yaml.safe_load(raw)`. When an import statement
|
|
1632
|
+
// binds the receiver, "no resolvable binding" is a FALSE sentence
|
|
1633
|
+
// about this row — there is one, and it leaves the repository. It
|
|
1634
|
+
// is now a node and an edge rather than a better-worded refusal.
|
|
1635
|
+
callExternal(source.id, external);
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
disclose(source, edgeType, `${reference.receiver}.${reference.name}`, file, reference.line, "unknown");
|
|
1565
1639
|
continue;
|
|
1566
1640
|
}
|
|
1567
1641
|
const method = byFullName
|
|
@@ -1781,6 +1855,11 @@ export function extract(input) {
|
|
|
1781
1855
|
const mounts = [];
|
|
1782
1856
|
const routeDecls = [];
|
|
1783
1857
|
const clientCalls = [];
|
|
1858
|
+
/**
|
|
1859
|
+
* Every file's client half, read but not yet judged — see the push site
|
|
1860
|
+
* for why deciding is deferred to after the loop.
|
|
1861
|
+
*/
|
|
1862
|
+
const pendingClients = [];
|
|
1784
1863
|
const djangoRouteEntries = [];
|
|
1785
1864
|
const djangoIncludeEntries = [];
|
|
1786
1865
|
const drfRegisterEntries = [];
|
|
@@ -1849,27 +1928,22 @@ export function extract(input) {
|
|
|
1849
1928
|
const moduleId = moduleNodeId.get(file);
|
|
1850
1929
|
return moduleId === undefined ? undefined : { id: moduleId };
|
|
1851
1930
|
};
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
const container = containerFor(call.scope);
|
|
1869
|
-
if (container === undefined)
|
|
1870
|
-
continue;
|
|
1871
|
-
clientCalls.push({ call, containerId: container.id });
|
|
1872
|
-
}
|
|
1931
|
+
// EMITTED AFTER THE LOOP, NOT HERE — DEC-164's origin rule needs every
|
|
1932
|
+
// file's bases before any one call site can be judged, and a base three
|
|
1933
|
+
// files away is still evidence about this call. Reading is per file;
|
|
1934
|
+
// deciding is per program. File order is preserved, so the ledger reads
|
|
1935
|
+
// the same as it did.
|
|
1936
|
+
pendingClients.push({
|
|
1937
|
+
file,
|
|
1938
|
+
calls,
|
|
1939
|
+
moduleOfImport: moduleOfImportIn(file),
|
|
1940
|
+
clientLocals,
|
|
1941
|
+
constants,
|
|
1942
|
+
localStrings,
|
|
1943
|
+
functionParams,
|
|
1944
|
+
containerFor,
|
|
1945
|
+
read,
|
|
1946
|
+
});
|
|
1873
1947
|
for (const declaration of parsed.declarations) {
|
|
1874
1948
|
for (const call of declaration.decoratorCalls ?? []) {
|
|
1875
1949
|
const route = routeFromDecorator(file, call, declaration.path);
|
|
@@ -1893,6 +1967,81 @@ export function extract(input) {
|
|
|
1893
1967
|
drfRegisterEntries.push(reg);
|
|
1894
1968
|
}
|
|
1895
1969
|
}
|
|
1970
|
+
// --- the client half, decided now that every file has been read --------
|
|
1971
|
+
//
|
|
1972
|
+
// DEC-164's own-origin rule admits an absolute call path only at an origin
|
|
1973
|
+
// some base in this same program already resolves to. The first read
|
|
1974
|
+
// produced those origins; the second re-reads only the files that could
|
|
1975
|
+
// change, and with no absolute base anywhere `origins` is empty and no
|
|
1976
|
+
// file is re-read at all.
|
|
1977
|
+
const origins = new Set();
|
|
1978
|
+
for (const pending of pendingClients) {
|
|
1979
|
+
for (const call of pending.read.calls) {
|
|
1980
|
+
if (call.baseOrigin !== undefined)
|
|
1981
|
+
origins.add(call.baseOrigin);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
for (const pending of pendingClients) {
|
|
1985
|
+
const couldChange = origins.size > 0 && pending.read.refusals.some((refusal) => refusal.reason === "callPathNotRelative");
|
|
1986
|
+
const read = couldChange
|
|
1987
|
+
? clientCallsIn(pending.file, pending.calls, pending.moduleOfImport, pending.clientLocals, pending.constants, pending.localStrings, pending.functionParams, origins)
|
|
1988
|
+
: pending.read;
|
|
1989
|
+
for (const refusal of read.refusals) {
|
|
1990
|
+
// DEC-164 is attributed to the caller that wrote the call; the ledger
|
|
1991
|
+
// row's own container is unchanged and still the module.
|
|
1992
|
+
const owner = pending.containerFor(refusal.scope);
|
|
1993
|
+
if (owner !== undefined && refusal.clientBase !== undefined && !clientBases.has(owner.id)) {
|
|
1994
|
+
clientBases.set(owner.id, refusal.clientBase);
|
|
1995
|
+
}
|
|
1996
|
+
const container = pending.containerFor([]);
|
|
1997
|
+
if (container === undefined)
|
|
1998
|
+
continue;
|
|
1999
|
+
disclose(container, "USES_API", refusal.rawTarget, refusal.file, refusal.line, refusal.reason,
|
|
2000
|
+
// Unset `refusalClass` stays legal and means unclassified — DEC-242 — so
|
|
2001
|
+
// `attrs` is omitted entirely rather than sent with an `undefined` field.
|
|
2002
|
+
refusal.refusalClass === undefined
|
|
2003
|
+
? undefined
|
|
2004
|
+
: {
|
|
2005
|
+
blockedBy: refusal.blockedBy,
|
|
2006
|
+
refusalClass: refusal.refusalClass,
|
|
2007
|
+
...(refusal.argumentKind === undefined ? {} : { argumentKind: refusal.argumentKind }),
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
// Pydantic parses written in this file, read once rather than per call
|
|
2011
|
+
// — golden 18's consumer half. Read HERE and not at the first pass
|
|
2012
|
+
// because `read` may be the SECOND read of this file (DEC-164's origin
|
|
2013
|
+
// rule re-reads a file whose refusals could change), and a model
|
|
2014
|
+
// resolved against the first read's calls would describe calls that no
|
|
2015
|
+
// longer exist.
|
|
2016
|
+
const parses = validateCalls(pending.calls);
|
|
2017
|
+
for (const call of read.calls) {
|
|
2018
|
+
const container = pending.containerFor(call.scope);
|
|
2019
|
+
if (container === undefined)
|
|
2020
|
+
continue;
|
|
2021
|
+
if (!clientBases.has(container.id))
|
|
2022
|
+
clientBases.set(container.id, call.clientBase);
|
|
2023
|
+
clientCalls.push({
|
|
2024
|
+
call,
|
|
2025
|
+
containerId: container.id,
|
|
2026
|
+
responseModel: modelParsing(parses, call.scope, call.client, call.rawPath),
|
|
2027
|
+
responseModelFile: pending.file,
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
// DEC-164, patched onto the caller now rather than at the end of the
|
|
2032
|
+
// function. `adapter-csharp` patches its own at the end and can, because
|
|
2033
|
+
// it has one exit; this function returns early at `reached < 3`, and a
|
|
2034
|
+
// patch after that line was **absent at R2 while the golden requires it
|
|
2035
|
+
// there** — the conformance sweep caught it as three `node-attr-mismatch`
|
|
2036
|
+
// findings at R2 only, which a single-level run would never have shown.
|
|
2037
|
+
// Here is the earliest point where every caller node exists and every
|
|
2038
|
+
// base has been decided.
|
|
2039
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
2040
|
+
const base = clientBases.get(nodes[i].id);
|
|
2041
|
+
if (base === undefined)
|
|
2042
|
+
continue;
|
|
2043
|
+
nodes[i] = { ...nodes[i], attrs: { ...nodes[i].attrs, clientBase: base } };
|
|
2044
|
+
}
|
|
1896
2045
|
/** child -> its parent and the prefix mounting it there. */
|
|
1897
2046
|
const parentOf = new Map();
|
|
1898
2047
|
for (const mount of mounts) {
|
|
@@ -2664,7 +2813,9 @@ export function extract(input) {
|
|
|
2664
2813
|
// rejected at the boundary as RESOLUTION_EXCEEDS_BATCH — the batch
|
|
2665
2814
|
// contract catching a claim stronger than the run that produced it, which
|
|
2666
2815
|
// is exactly what it is for.
|
|
2667
|
-
for (const { call, containerId } of input.reached >= 2
|
|
2816
|
+
for (const { call, containerId, responseModel, responseModelFile } of input.reached >= 2
|
|
2817
|
+
? clientCalls
|
|
2818
|
+
: []) {
|
|
2668
2819
|
const endpointId = nodeId(scope, "API_ENDPOINT", endpointQsp(call.method, call.path), null);
|
|
2669
2820
|
if (!routeSeen.has(endpointId)) {
|
|
2670
2821
|
routeSeen.add(endpointId);
|
|
@@ -2680,6 +2831,26 @@ export function extract(input) {
|
|
|
2680
2831
|
attrs: { method: call.method, pathTemplate: normaliseEndpointPath(call.path) },
|
|
2681
2832
|
});
|
|
2682
2833
|
}
|
|
2834
|
+
/**
|
|
2835
|
+
* Golden 18's consumer half. Gated on `input.reached >= 3` INSIDE the
|
|
2836
|
+
* attrs rather than by an early return above: this loop runs from R2, so
|
|
2837
|
+
* a guard placed one level up would be green at R3 and silently wrong at
|
|
2838
|
+
* R2 — the monotonic sweep's own failure mode. At R2 the key is simply
|
|
2839
|
+
* absent, which is the honest answer.
|
|
2840
|
+
*
|
|
2841
|
+
* R3 because the shape is a Pydantic DECLARATION — types stated, not
|
|
2842
|
+
* inferred — which is DEC-202's rule and the same ground `adapter-java`'s
|
|
2843
|
+
* JPA shapes and `adapter-sql`'s schema stand on. The edge itself stays
|
|
2844
|
+
* at 2 (path resolution); `shapeResolution` carries the shape's own
|
|
2845
|
+
* evidence so the contract engine does not cap it at the weaker of the
|
|
2846
|
+
* two facts this edge holds (DEC-058).
|
|
2847
|
+
*/
|
|
2848
|
+
const declaredShape = input.reached >= 3 && responseModel !== null
|
|
2849
|
+
? followName(responseModelFile, responseModel)
|
|
2850
|
+
: undefined;
|
|
2851
|
+
const shapeAttrs = declaredShape !== undefined && declaredShape.type === "DTO"
|
|
2852
|
+
? { responseType: declaredShape.id, shapeResolution: 3 }
|
|
2853
|
+
: {};
|
|
2683
2854
|
push({
|
|
2684
2855
|
from: containerId,
|
|
2685
2856
|
to: endpointId,
|
|
@@ -2689,6 +2860,7 @@ export function extract(input) {
|
|
|
2689
2860
|
file: call.file,
|
|
2690
2861
|
line: call.line,
|
|
2691
2862
|
written: call.rawPath,
|
|
2863
|
+
...shapeAttrs,
|
|
2692
2864
|
},
|
|
2693
2865
|
}, 2);
|
|
2694
2866
|
}
|