@rvoh/psychic 3.8.6 → 3.9.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/cjs/src/bin/helpers/OpenApiSpecDiff.js +4 -0
- package/dist/cjs/src/openapi-renderer/app.js +2 -0
- package/dist/cjs/src/openapi-renderer/body-segment.js +3 -0
- package/dist/cjs/src/openapi-renderer/endpoint.js +10 -0
- package/dist/cjs/src/openapi-renderer/helpers/OpenapiPayloadValidator.js +2 -0
- package/dist/cjs/src/openapi-renderer/helpers/buildDreamRequestBodyShape.js +8 -2
- package/dist/cjs/src/openapi-renderer/helpers/dreamColumnOpenapiShape.js +1 -0
- package/dist/cjs/src/openapi-renderer/helpers/legacyImplicitRequestBodyParamsConfig.js +9 -0
- package/dist/esm/src/bin/helpers/OpenApiSpecDiff.js +4 -0
- package/dist/esm/src/openapi-renderer/app.js +2 -0
- package/dist/esm/src/openapi-renderer/body-segment.js +3 -0
- package/dist/esm/src/openapi-renderer/endpoint.js +10 -0
- package/dist/esm/src/openapi-renderer/helpers/OpenapiPayloadValidator.js +2 -0
- package/dist/esm/src/openapi-renderer/helpers/buildDreamRequestBodyShape.js +8 -2
- package/dist/esm/src/openapi-renderer/helpers/dreamColumnOpenapiShape.js +1 -0
- package/dist/esm/src/openapi-renderer/helpers/legacyImplicitRequestBodyParamsConfig.js +9 -0
- package/dist/types/src/bin/helpers/OpenApiSpecDiff.d.ts +5 -1
- package/dist/types/src/openapi-renderer/body-segment.d.ts +1 -0
- package/dist/types/src/openapi-renderer/endpoint.d.ts +1 -0
- package/dist/types/src/openapi-renderer/helpers/buildDreamRequestBodyShape.d.ts +7 -0
- package/dist/types/src/openapi-renderer/helpers/legacyImplicitRequestBodyParamsConfig.d.ts +6 -0
- package/dist/types/src/psychic-app/index.d.ts +23 -0
- package/package.json +1 -1
|
@@ -189,6 +189,10 @@ export class OpenApiSpecDiff {
|
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
191
|
* Retrieves head branch content for a file
|
|
192
|
+
*
|
|
193
|
+
* Marked `protected` so tests can supply a deterministic baseline instead of
|
|
194
|
+
* the live head branch. Production code never overrides this.
|
|
195
|
+
*
|
|
192
196
|
* @param absoluteFilePath - Absolute path to the file
|
|
193
197
|
*/
|
|
194
198
|
getHeadBranchContent(absoluteFilePath) {
|
|
@@ -7,6 +7,7 @@ import PsychicApp from '../psychic-app/index.js';
|
|
|
7
7
|
import { HttpMethods } from '../router/types.js';
|
|
8
8
|
import { DEFAULT_OPENAPI_COMPONENT_RESPONSES, DEFAULT_OPENAPI_COMPONENT_SCHEMAS } from './defaults.js';
|
|
9
9
|
import { MissingControllerActionPairingInRoutes, } from './endpoint.js';
|
|
10
|
+
import legacyImplicitRequestBodyParamsConfig from './helpers/legacyImplicitRequestBodyParamsConfig.js';
|
|
10
11
|
import suppressResponseEnumsConfig from './helpers/suppressResponseEnumsConfig.js';
|
|
11
12
|
const debugEnabled = debuglog('psychic').enabled;
|
|
12
13
|
export default class OpenapiAppRenderer {
|
|
@@ -47,6 +48,7 @@ export default class OpenapiAppRenderer {
|
|
|
47
48
|
const renderOpts = {
|
|
48
49
|
casing: 'camel',
|
|
49
50
|
suppressResponseEnums: suppressResponseEnumsConfig(openapiName),
|
|
51
|
+
legacyImplicitRequestBodyParams: legacyImplicitRequestBodyParamsConfig(openapiName),
|
|
50
52
|
};
|
|
51
53
|
const alreadyExtractedDescendantSerializers = {};
|
|
52
54
|
const renderedSchemasOpenapi = {};
|
|
@@ -54,6 +54,7 @@ export default class OpenapiSegmentExpander {
|
|
|
54
54
|
bodySegment;
|
|
55
55
|
casing;
|
|
56
56
|
suppressResponseEnums;
|
|
57
|
+
legacyImplicitRequestBodyParams;
|
|
57
58
|
target;
|
|
58
59
|
source;
|
|
59
60
|
/**
|
|
@@ -66,6 +67,7 @@ export default class OpenapiSegmentExpander {
|
|
|
66
67
|
this.bodySegment = bodySegment;
|
|
67
68
|
this.casing = renderOpts.casing;
|
|
68
69
|
this.suppressResponseEnums = renderOpts.suppressResponseEnums;
|
|
70
|
+
this.legacyImplicitRequestBodyParams = renderOpts.legacyImplicitRequestBodyParams;
|
|
69
71
|
this.target = target;
|
|
70
72
|
this.source = source ?? 'unknown';
|
|
71
73
|
}
|
|
@@ -489,6 +491,7 @@ The following values will be allowed:
|
|
|
489
491
|
including: ref.including,
|
|
490
492
|
required: ref.required,
|
|
491
493
|
combining: ref.combining,
|
|
494
|
+
legacyImplicitRequestBodyParams: this.legacyImplicitRequestBodyParams,
|
|
492
495
|
}, this.source);
|
|
493
496
|
return this.recursivelyParseBody(paramsShape);
|
|
494
497
|
}
|
|
@@ -529,7 +529,17 @@ export default class OpenapiEndpointRenderer {
|
|
|
529
529
|
including: including,
|
|
530
530
|
required: required,
|
|
531
531
|
combining: combining,
|
|
532
|
+
legacyImplicitRequestBodyParams: renderOpts.legacyImplicitRequestBodyParams,
|
|
532
533
|
}, source);
|
|
534
|
+
// When the model-derived shape contributes no properties and no `required`
|
|
535
|
+
// (e.g. the new none-default with no `params`/`only`/`combining`), there is
|
|
536
|
+
// nothing meaningful to advertise. Fall back to `defaultRequestBody()`, which
|
|
537
|
+
// omits the body entirely unless body-level pagination params are present.
|
|
538
|
+
const shapeProperties = paramsShape.properties;
|
|
539
|
+
const shapeRequired = paramsShape.required;
|
|
540
|
+
if (Object.keys(shapeProperties ?? {}).length === 0 && !shapeRequired) {
|
|
541
|
+
return this.defaultRequestBody();
|
|
542
|
+
}
|
|
533
543
|
let processedSchema = new OpenapiSegmentExpander(paramsShape, {
|
|
534
544
|
renderOpts,
|
|
535
545
|
target: 'request',
|
|
@@ -2,6 +2,7 @@ import OpenapiRequestValidationFailure from '../../error/openapi/OpenapiRequestV
|
|
|
2
2
|
import OpenapiResponseValidationFailure from '../../error/openapi/OpenapResponseValidationFailure.js';
|
|
3
3
|
import { createValidator, formatAjvErrors, } from '../../helpers/validateOpenApiSchema.js';
|
|
4
4
|
import PsychicApp from '../../psychic-app/index.js';
|
|
5
|
+
import legacyImplicitRequestBodyParamsConfig from './legacyImplicitRequestBodyParamsConfig.js';
|
|
5
6
|
import suppressResponseEnumsConfig from './suppressResponseEnumsConfig.js';
|
|
6
7
|
import { cacheValidator, getCachedValidator } from './validator-cache.js';
|
|
7
8
|
/**
|
|
@@ -207,6 +208,7 @@ export default class OpenapiPayloadValidator {
|
|
|
207
208
|
return {
|
|
208
209
|
casing: 'camel',
|
|
209
210
|
suppressResponseEnums: suppressResponseEnumsConfig(this.openapiName),
|
|
211
|
+
legacyImplicitRequestBodyParams: legacyImplicitRequestBodyParamsConfig(this.openapiName),
|
|
210
212
|
};
|
|
211
213
|
}
|
|
212
214
|
/**
|
|
@@ -11,9 +11,15 @@ import { dreamColumnOpenapiShape } from './dreamColumnOpenapiShape.js';
|
|
|
11
11
|
* truth keeps top-level and nested semantics identical.
|
|
12
12
|
*/
|
|
13
13
|
export default function buildDreamRequestBodyShape(dreamClass, opts, source) {
|
|
14
|
-
const { params, only, including, required, combining } = opts;
|
|
14
|
+
const { params, only, including, required, combining, legacyImplicitRequestBodyParams } = opts;
|
|
15
|
+
// When neither `params` nor `only` is provided, the default is to resolve to
|
|
16
|
+
// NO model columns (an empty allowlist), rather than implicitly exposing every
|
|
17
|
+
// param-safe column. The legacy implicit-all behavior can be re-activated via
|
|
18
|
+
// the `legacyImplicitRequestBodyParams` opt-in.
|
|
19
|
+
const resolvedOnly = params ?? only;
|
|
20
|
+
const onlyForResolution = resolvedOnly === undefined && !legacyImplicitRequestBodyParams ? [] : resolvedOnly;
|
|
15
21
|
const paramSafeColumns = openapiParamNamesForDreamClass(dreamClass, {
|
|
16
|
-
only:
|
|
22
|
+
only: onlyForResolution,
|
|
17
23
|
including,
|
|
18
24
|
});
|
|
19
25
|
const paramsShape = {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import openapiOpts from './openapiOpts.js';
|
|
2
|
+
/**
|
|
3
|
+
* returns whether the named openapi config opts in to the legacy behavior of
|
|
4
|
+
* implicitly exposing all param-safe columns when a model-derived request body
|
|
5
|
+
* is given no `params`/`only`.
|
|
6
|
+
*/
|
|
7
|
+
export default function legacyImplicitRequestBodyParamsConfig(openapiName) {
|
|
8
|
+
return !!openapiOpts(openapiName)?.legacyImplicitRequestBodyParams;
|
|
9
|
+
}
|
|
@@ -189,6 +189,10 @@ export class OpenApiSpecDiff {
|
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
191
|
* Retrieves head branch content for a file
|
|
192
|
+
*
|
|
193
|
+
* Marked `protected` so tests can supply a deterministic baseline instead of
|
|
194
|
+
* the live head branch. Production code never overrides this.
|
|
195
|
+
*
|
|
192
196
|
* @param absoluteFilePath - Absolute path to the file
|
|
193
197
|
*/
|
|
194
198
|
getHeadBranchContent(absoluteFilePath) {
|
|
@@ -7,6 +7,7 @@ import PsychicApp from '../psychic-app/index.js';
|
|
|
7
7
|
import { HttpMethods } from '../router/types.js';
|
|
8
8
|
import { DEFAULT_OPENAPI_COMPONENT_RESPONSES, DEFAULT_OPENAPI_COMPONENT_SCHEMAS } from './defaults.js';
|
|
9
9
|
import { MissingControllerActionPairingInRoutes, } from './endpoint.js';
|
|
10
|
+
import legacyImplicitRequestBodyParamsConfig from './helpers/legacyImplicitRequestBodyParamsConfig.js';
|
|
10
11
|
import suppressResponseEnumsConfig from './helpers/suppressResponseEnumsConfig.js';
|
|
11
12
|
const debugEnabled = debuglog('psychic').enabled;
|
|
12
13
|
export default class OpenapiAppRenderer {
|
|
@@ -47,6 +48,7 @@ export default class OpenapiAppRenderer {
|
|
|
47
48
|
const renderOpts = {
|
|
48
49
|
casing: 'camel',
|
|
49
50
|
suppressResponseEnums: suppressResponseEnumsConfig(openapiName),
|
|
51
|
+
legacyImplicitRequestBodyParams: legacyImplicitRequestBodyParamsConfig(openapiName),
|
|
50
52
|
};
|
|
51
53
|
const alreadyExtractedDescendantSerializers = {};
|
|
52
54
|
const renderedSchemasOpenapi = {};
|
|
@@ -54,6 +54,7 @@ export default class OpenapiSegmentExpander {
|
|
|
54
54
|
bodySegment;
|
|
55
55
|
casing;
|
|
56
56
|
suppressResponseEnums;
|
|
57
|
+
legacyImplicitRequestBodyParams;
|
|
57
58
|
target;
|
|
58
59
|
source;
|
|
59
60
|
/**
|
|
@@ -66,6 +67,7 @@ export default class OpenapiSegmentExpander {
|
|
|
66
67
|
this.bodySegment = bodySegment;
|
|
67
68
|
this.casing = renderOpts.casing;
|
|
68
69
|
this.suppressResponseEnums = renderOpts.suppressResponseEnums;
|
|
70
|
+
this.legacyImplicitRequestBodyParams = renderOpts.legacyImplicitRequestBodyParams;
|
|
69
71
|
this.target = target;
|
|
70
72
|
this.source = source ?? 'unknown';
|
|
71
73
|
}
|
|
@@ -489,6 +491,7 @@ The following values will be allowed:
|
|
|
489
491
|
including: ref.including,
|
|
490
492
|
required: ref.required,
|
|
491
493
|
combining: ref.combining,
|
|
494
|
+
legacyImplicitRequestBodyParams: this.legacyImplicitRequestBodyParams,
|
|
492
495
|
}, this.source);
|
|
493
496
|
return this.recursivelyParseBody(paramsShape);
|
|
494
497
|
}
|
|
@@ -529,7 +529,17 @@ export default class OpenapiEndpointRenderer {
|
|
|
529
529
|
including: including,
|
|
530
530
|
required: required,
|
|
531
531
|
combining: combining,
|
|
532
|
+
legacyImplicitRequestBodyParams: renderOpts.legacyImplicitRequestBodyParams,
|
|
532
533
|
}, source);
|
|
534
|
+
// When the model-derived shape contributes no properties and no `required`
|
|
535
|
+
// (e.g. the new none-default with no `params`/`only`/`combining`), there is
|
|
536
|
+
// nothing meaningful to advertise. Fall back to `defaultRequestBody()`, which
|
|
537
|
+
// omits the body entirely unless body-level pagination params are present.
|
|
538
|
+
const shapeProperties = paramsShape.properties;
|
|
539
|
+
const shapeRequired = paramsShape.required;
|
|
540
|
+
if (Object.keys(shapeProperties ?? {}).length === 0 && !shapeRequired) {
|
|
541
|
+
return this.defaultRequestBody();
|
|
542
|
+
}
|
|
533
543
|
let processedSchema = new OpenapiSegmentExpander(paramsShape, {
|
|
534
544
|
renderOpts,
|
|
535
545
|
target: 'request',
|
|
@@ -2,6 +2,7 @@ import OpenapiRequestValidationFailure from '../../error/openapi/OpenapiRequestV
|
|
|
2
2
|
import OpenapiResponseValidationFailure from '../../error/openapi/OpenapResponseValidationFailure.js';
|
|
3
3
|
import { createValidator, formatAjvErrors, } from '../../helpers/validateOpenApiSchema.js';
|
|
4
4
|
import PsychicApp from '../../psychic-app/index.js';
|
|
5
|
+
import legacyImplicitRequestBodyParamsConfig from './legacyImplicitRequestBodyParamsConfig.js';
|
|
5
6
|
import suppressResponseEnumsConfig from './suppressResponseEnumsConfig.js';
|
|
6
7
|
import { cacheValidator, getCachedValidator } from './validator-cache.js';
|
|
7
8
|
/**
|
|
@@ -207,6 +208,7 @@ export default class OpenapiPayloadValidator {
|
|
|
207
208
|
return {
|
|
208
209
|
casing: 'camel',
|
|
209
210
|
suppressResponseEnums: suppressResponseEnumsConfig(this.openapiName),
|
|
211
|
+
legacyImplicitRequestBodyParams: legacyImplicitRequestBodyParamsConfig(this.openapiName),
|
|
210
212
|
};
|
|
211
213
|
}
|
|
212
214
|
/**
|
|
@@ -11,9 +11,15 @@ import { dreamColumnOpenapiShape } from './dreamColumnOpenapiShape.js';
|
|
|
11
11
|
* truth keeps top-level and nested semantics identical.
|
|
12
12
|
*/
|
|
13
13
|
export default function buildDreamRequestBodyShape(dreamClass, opts, source) {
|
|
14
|
-
const { params, only, including, required, combining } = opts;
|
|
14
|
+
const { params, only, including, required, combining, legacyImplicitRequestBodyParams } = opts;
|
|
15
|
+
// When neither `params` nor `only` is provided, the default is to resolve to
|
|
16
|
+
// NO model columns (an empty allowlist), rather than implicitly exposing every
|
|
17
|
+
// param-safe column. The legacy implicit-all behavior can be re-activated via
|
|
18
|
+
// the `legacyImplicitRequestBodyParams` opt-in.
|
|
19
|
+
const resolvedOnly = params ?? only;
|
|
20
|
+
const onlyForResolution = resolvedOnly === undefined && !legacyImplicitRequestBodyParams ? [] : resolvedOnly;
|
|
15
21
|
const paramSafeColumns = openapiParamNamesForDreamClass(dreamClass, {
|
|
16
|
-
only:
|
|
22
|
+
only: onlyForResolution,
|
|
17
23
|
including,
|
|
18
24
|
});
|
|
19
25
|
const paramsShape = {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import openapiOpts from './openapiOpts.js';
|
|
2
|
+
/**
|
|
3
|
+
* returns whether the named openapi config opts in to the legacy behavior of
|
|
4
|
+
* implicitly exposing all param-safe columns when a model-derived request body
|
|
5
|
+
* is given no `params`/`only`.
|
|
6
|
+
*/
|
|
7
|
+
export default function legacyImplicitRequestBodyParamsConfig(openapiName) {
|
|
8
|
+
return !!openapiOpts(openapiName)?.legacyImplicitRequestBodyParams;
|
|
9
|
+
}
|
|
@@ -96,9 +96,13 @@ export declare class OpenApiSpecDiff {
|
|
|
96
96
|
private createTempFilePath;
|
|
97
97
|
/**
|
|
98
98
|
* Retrieves head branch content for a file
|
|
99
|
+
*
|
|
100
|
+
* Marked `protected` so tests can supply a deterministic baseline instead of
|
|
101
|
+
* the live head branch. Production code never overrides this.
|
|
102
|
+
*
|
|
99
103
|
* @param absoluteFilePath - Absolute path to the file
|
|
100
104
|
*/
|
|
101
|
-
|
|
105
|
+
protected getHeadBranchContent(absoluteFilePath: string): string;
|
|
102
106
|
/**
|
|
103
107
|
* Compares a single OpenAPI file against head branch
|
|
104
108
|
*/
|
|
@@ -11,6 +11,7 @@ import { OpenapiBodySegment, ReferencedSerializersAndOpenapiEndpointResponse, Se
|
|
|
11
11
|
export interface OpenapiRenderOpts {
|
|
12
12
|
casing: SerializerCasing;
|
|
13
13
|
suppressResponseEnums: boolean;
|
|
14
|
+
legacyImplicitRequestBodyParams: boolean;
|
|
14
15
|
}
|
|
15
16
|
export interface ToPathObjectOpts {
|
|
16
17
|
openapiName: string;
|
|
@@ -6,6 +6,13 @@ export interface BuildDreamRequestBodyShapeOpts {
|
|
|
6
6
|
including?: readonly string[] | undefined;
|
|
7
7
|
required?: readonly string[] | undefined;
|
|
8
8
|
combining?: Record<string, unknown> | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* When true, restores the legacy behavior of resolving to ALL param-safe
|
|
11
|
+
* columns when no `params`/`only` are provided. When false/undefined (the
|
|
12
|
+
* default), an absent `params`/`only` resolves to NO model columns, so that
|
|
13
|
+
* database-level structure is not implicitly leaked into the OpenAPI shape.
|
|
14
|
+
*/
|
|
15
|
+
legacyImplicitRequestBodyParams?: boolean | undefined;
|
|
9
16
|
}
|
|
10
17
|
/**
|
|
11
18
|
* @internal
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* returns whether the named openapi config opts in to the legacy behavior of
|
|
3
|
+
* implicitly exposing all param-safe columns when a model-derived request body
|
|
4
|
+
* is given no `params`/`only`.
|
|
5
|
+
*/
|
|
6
|
+
export default function legacyImplicitRequestBodyParamsConfig(openapiName: string): boolean;
|
|
@@ -356,6 +356,29 @@ interface PsychicOpenapiBaseOptions {
|
|
|
356
356
|
* ```
|
|
357
357
|
*/
|
|
358
358
|
suppressResponseEnums?: boolean;
|
|
359
|
+
/**
|
|
360
|
+
* @deprecated escape hatch — prefer explicit `params`/`only` on each request body.
|
|
361
|
+
*
|
|
362
|
+
* When true, restores the legacy behavior where a model-derived `requestBody`
|
|
363
|
+
* (or nested `$dream`/`for:` segment) given no `params`/`only` implicitly
|
|
364
|
+
* exposes ALL param-safe columns of the model.
|
|
365
|
+
*
|
|
366
|
+
* As of 3.9.0 the default flipped: an absent `params`/`only` now resolves to
|
|
367
|
+
* NO model columns, so that database-level structure is not implicitly leaked
|
|
368
|
+
* into the OpenAPI shape (an attacker could otherwise infer the column set).
|
|
369
|
+
* A top-level model-derived `requestBody` with nothing to contribute is
|
|
370
|
+
* omitted entirely; a nested `$dream`/`for:` segment renders an empty object.
|
|
371
|
+
*
|
|
372
|
+
* Set this to true to keep the pre-3.9.0 implicit-all behavior while you
|
|
373
|
+
* migrate to explicit allowlists.
|
|
374
|
+
*
|
|
375
|
+
* ```ts
|
|
376
|
+
* psy.set('openapi', {
|
|
377
|
+
* legacyImplicitRequestBodyParams: true,
|
|
378
|
+
* })
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
legacyImplicitRequestBodyParams?: boolean;
|
|
359
382
|
/**
|
|
360
383
|
* When true, psychic will use the `openapi-typescript` package
|
|
361
384
|
* to read this openapi.json file and create typescript interfaces
|